tags, folder rename, other stuff
This commit is contained in:
+92
-21
@@ -28,7 +28,7 @@ import TagsPanel from './tags/TagsPanel';
|
||||
import CorrespondentsPanel from './correspondents/CorrespondentsPanel';
|
||||
import { CORRESPONDENT_ROLES } from './constants/correspondents';
|
||||
import { DownloadIcon } from './ui/icons';
|
||||
import { generateRandomTagColor } from './utils/colors';
|
||||
import TagManager from './tag_manager';
|
||||
import { formatFileSize } from './utils/format';
|
||||
import Sidebar from './sidebar/Sidebar';
|
||||
import DocumentsTable, { FilterBar } from './documents/DocumentsTable';
|
||||
@@ -385,6 +385,17 @@ const AppLayout = () => {
|
||||
}
|
||||
const assetManager = assetManagerRef.current;
|
||||
|
||||
const tagManagerRef = useRef(null);
|
||||
if (!tagManagerRef.current) {
|
||||
tagManagerRef.current = new TagManager();
|
||||
}
|
||||
const tagManager = tagManagerRef.current;
|
||||
|
||||
const buildTagPayload = useCallback(
|
||||
({ label, color } = {}) => tagManager.buildPayload({ label, color }),
|
||||
[tagManager],
|
||||
);
|
||||
|
||||
const getDocumentAsset = useCallback((doc, type) => {
|
||||
if (!doc || !type) return null;
|
||||
return getAssetFromVersion(doc.current_version || null, type);
|
||||
@@ -1238,12 +1249,8 @@ const AppLayout = () => {
|
||||
);
|
||||
|
||||
const handleTagCreate = useCallback(
|
||||
async ({ label, color }) => {
|
||||
const trimmed = (label || '').trim();
|
||||
if (!trimmed) {
|
||||
throw new Error('Tag label is required.');
|
||||
}
|
||||
const payload = { label: trimmed, color: color || generateRandomTagColor() };
|
||||
async ({ label, color } = {}) => {
|
||||
const payload = tagManager.buildPayload({ label, color });
|
||||
try {
|
||||
await api.post('/tags', payload);
|
||||
await refreshTags();
|
||||
@@ -1254,7 +1261,7 @@ const AppLayout = () => {
|
||||
throw new Error(message);
|
||||
}
|
||||
},
|
||||
[api, refreshTags, notifyApiError],
|
||||
[api, refreshTags, notifyApiError, setStatusMessage, tagManager],
|
||||
);
|
||||
|
||||
const handleCorrespondentUpdate = useCallback(
|
||||
@@ -1783,7 +1790,8 @@ const AppLayout = () => {
|
||||
for (const label of normalized) {
|
||||
let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null;
|
||||
if (!tag) {
|
||||
const { data } = await api.post('/tags', { label, color: null });
|
||||
const payload = tagManager.buildPayload({ label });
|
||||
const { data } = await api.post('/tags', payload);
|
||||
tag = data;
|
||||
await refreshTags();
|
||||
}
|
||||
@@ -1829,6 +1837,7 @@ const AppLayout = () => {
|
||||
refreshCurrentFolder,
|
||||
notifyApiError,
|
||||
setLoading,
|
||||
tagManager,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -2938,13 +2947,13 @@ const AppLayout = () => {
|
||||
|
||||
const handleTagAdd = useCallback(
|
||||
async (document, label, input) => {
|
||||
let tag = tags.find((item) => item.label.toLowerCase() === label.toLowerCase()) || null;
|
||||
const normalizedLabel = tagManager.normalizeLabel(label);
|
||||
let tag =
|
||||
tags.find((item) => item.label.toLowerCase() === normalizedLabel.toLowerCase()) || null;
|
||||
try {
|
||||
if (!tag) {
|
||||
const { data } = await api.post('/tags', {
|
||||
label,
|
||||
color: generateRandomTagColor(),
|
||||
});
|
||||
const payload = tagManager.buildPayload({ label: normalizedLabel });
|
||||
const { data } = await api.post('/tags', payload);
|
||||
tag = data;
|
||||
await refreshTags();
|
||||
}
|
||||
@@ -2956,13 +2965,7 @@ const AppLayout = () => {
|
||||
notifyApiError(error, 'Failed to assign tag.');
|
||||
}
|
||||
},
|
||||
[
|
||||
tags,
|
||||
refreshTags,
|
||||
refreshCurrentFolder,
|
||||
notifyApiError,
|
||||
setStatusMessage,
|
||||
],
|
||||
[tags, refreshTags, refreshCurrentFolder, notifyApiError, setStatusMessage, tagManager],
|
||||
);
|
||||
|
||||
const handleDocumentTagAttach = useCallback(
|
||||
@@ -3054,6 +3057,63 @@ const AppLayout = () => {
|
||||
],
|
||||
);
|
||||
|
||||
const handleFolderRename = useCallback(
|
||||
async (folderId, nextName) => {
|
||||
if (!token) {
|
||||
setStatusMessage('Log in to rename folders.', 'error');
|
||||
return false;
|
||||
}
|
||||
if (!folderId || folderId === 'root') {
|
||||
setStatusMessage('The root folder cannot be renamed.', 'error');
|
||||
return false;
|
||||
}
|
||||
const trimmed = (nextName || '').trim();
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Folder name cannot be empty.', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.patch(`/folders/${folderId}`, { name: trimmed });
|
||||
|
||||
setFolderNodes((prev) => {
|
||||
const next = new Map(prev);
|
||||
const node = next.get(folderId);
|
||||
if (node) {
|
||||
next.set(folderId, { ...node, name: trimmed });
|
||||
}
|
||||
return next;
|
||||
});
|
||||
|
||||
setFolderContents((prev) => {
|
||||
if (!prev.has(folderId)) {
|
||||
return prev;
|
||||
}
|
||||
const next = new Map(prev);
|
||||
const existing = next.get(folderId) || {};
|
||||
const folderInfo = existing.folder
|
||||
? { ...existing.folder, name: trimmed }
|
||||
: { id: folderId, name: trimmed };
|
||||
next.set(folderId, { ...existing, folder: folderInfo });
|
||||
return next;
|
||||
});
|
||||
|
||||
setCurrentFolder((prev) => (prev?.id === folderId ? { ...prev, name: trimmed } : prev));
|
||||
|
||||
setStatusMessage('Folder renamed.', 'success');
|
||||
return true;
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.error || 'Failed to rename folder.';
|
||||
notifyApiError(error, message);
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[api, token, notifyApiError, setStatusMessage],
|
||||
);
|
||||
|
||||
const handleFolderCreate = useCallback(
|
||||
async (name, onSuccess) => {
|
||||
if (!token) {
|
||||
@@ -3769,6 +3829,7 @@ const AppLayout = () => {
|
||||
onDragOver: folderClickHandlers.onDragOver,
|
||||
onDragLeave: folderClickHandlers.onDragLeave,
|
||||
onDeleteFolder: handleFolderDelete,
|
||||
onRenameFolder: handleFolderRename,
|
||||
selectedFolder,
|
||||
onFolderDragStart: handleFolderDragStart,
|
||||
onFolderDragEnd: handleFolderDragEnd,
|
||||
@@ -3810,9 +3871,11 @@ const AppLayout = () => {
|
||||
onFolderDragEnd: handleFolderDragEnd,
|
||||
draggedFolderId,
|
||||
onFolderDelete: handleFolderDelete,
|
||||
onFolderRename: handleFolderRename,
|
||||
onDocumentRowClick: handleDocumentRowClick,
|
||||
onDocumentOpen: openDocumentPreview,
|
||||
onDocumentDelete: handleDocumentDelete,
|
||||
onDocumentRename: handleDocumentTitleUpdate,
|
||||
selectedDocumentIds,
|
||||
focusedDocumentId,
|
||||
focusedRowKey,
|
||||
@@ -3830,6 +3893,12 @@ const AppLayout = () => {
|
||||
doc?.current_version?.download_path
|
||||
? resolveApiPath(doc.current_version.download_path)
|
||||
: null,
|
||||
onTagClick: (tagId) => {
|
||||
if (!tagId) return;
|
||||
setActiveTagFilters((previous) =>
|
||||
previous.includes(tagId) ? previous : previous.concat([tagId]),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const detailPanelProps = {
|
||||
@@ -3875,6 +3944,7 @@ const AppLayout = () => {
|
||||
onRemoveTagFromDocument: handleTagRemove,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
prepareTagPayload: buildTagPayload,
|
||||
}),
|
||||
[
|
||||
documents,
|
||||
@@ -3891,6 +3961,7 @@ const AppLayout = () => {
|
||||
handleTagRemove,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
buildTagPayload,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user