move folder creation to sidebar
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
|||||||
FolderIcon,
|
FolderIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
RefreshIcon,
|
RefreshIcon,
|
||||||
FolderPlusIcon,
|
|
||||||
MinusVerticalIcon,
|
MinusVerticalIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
@@ -199,8 +198,6 @@ const DocumentsTable = ({
|
|||||||
currentFolderName,
|
currentFolderName,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
onShowSkeuoWorkspace = () => {},
|
onShowSkeuoWorkspace = () => {},
|
||||||
onRequestCreateFolder,
|
|
||||||
creatingFolder = false,
|
|
||||||
subfolders,
|
subfolders,
|
||||||
documents,
|
documents,
|
||||||
searchResults,
|
searchResults,
|
||||||
@@ -512,13 +509,6 @@ const DocumentsTable = ({
|
|||||||
<ViewGridIcon className="view-toggle__icon" size={18} />
|
<ViewGridIcon className="view-toggle__icon" size={18} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onRequestCreateFolder}
|
|
||||||
disabled={creatingFolder}
|
|
||||||
>
|
|
||||||
{creatingFolder ? 'Creating…' : 'New folder'}
|
|
||||||
</button>
|
|
||||||
<button className="secondary" onClick={onRefresh}>
|
<button className="secondary" onClick={onRefresh}>
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
@@ -1056,8 +1046,6 @@ export { DocumentThumbnailImage };
|
|||||||
export const createDocumentsTableHeaderActions = ({
|
export const createDocumentsTableHeaderActions = ({
|
||||||
viewMode,
|
viewMode,
|
||||||
onViewModeChange,
|
onViewModeChange,
|
||||||
onRequestCreateFolder,
|
|
||||||
creatingFolder,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
onShowSkeuoWorkspace,
|
onShowSkeuoWorkspace,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -1088,16 +1076,6 @@ export const createDocumentsTableHeaderActions = ({
|
|||||||
<span className="main-content__actions-divider" aria-hidden="true">
|
<span className="main-content__actions-divider" aria-hidden="true">
|
||||||
<MinusVerticalIcon />
|
<MinusVerticalIcon />
|
||||||
</span>
|
</span>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="icon-button"
|
|
||||||
onClick={onRequestCreateFolder}
|
|
||||||
disabled={creatingFolder}
|
|
||||||
aria-label={creatingFolder ? 'Creating folder…' : 'Create folder'}
|
|
||||||
title={creatingFolder ? 'Creating folder…' : 'Create folder'}
|
|
||||||
>
|
|
||||||
<FolderPlusIcon />
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="icon-button"
|
className="icon-button"
|
||||||
@@ -1127,8 +1105,6 @@ export const createDocumentsSurface = ({
|
|||||||
searchResults,
|
searchResults,
|
||||||
viewMode,
|
viewMode,
|
||||||
onViewModeChange,
|
onViewModeChange,
|
||||||
onRequestCreateFolder,
|
|
||||||
creatingFolder,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
onShowSkeuoWorkspace,
|
onShowSkeuoWorkspace,
|
||||||
} = tableProps;
|
} = tableProps;
|
||||||
@@ -1141,8 +1117,6 @@ export const createDocumentsSurface = ({
|
|||||||
const actions = createDocumentsTableHeaderActions({
|
const actions = createDocumentsTableHeaderActions({
|
||||||
viewMode,
|
viewMode,
|
||||||
onViewModeChange,
|
onViewModeChange,
|
||||||
onRequestCreateFolder,
|
|
||||||
creatingFolder,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
onShowSkeuoWorkspace,
|
onShowSkeuoWorkspace,
|
||||||
});
|
});
|
||||||
|
|||||||
+27
-130
@@ -440,14 +440,10 @@ const AppLayout = () => {
|
|||||||
[reportApiError],
|
[reportApiError],
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [isCreateFolderModalOpen, setCreateFolderModalOpen] = useState(false);
|
|
||||||
const [isTagsModalOpen, setTagsModalOpen] = useState(false);
|
const [isTagsModalOpen, setTagsModalOpen] = useState(false);
|
||||||
const [isCorrespondentsModalOpen, setCorrespondentsModalOpen] = useState(false);
|
const [isCorrespondentsModalOpen, setCorrespondentsModalOpen] = useState(false);
|
||||||
const [isSettingsModalOpen, setSettingsModalOpen] = useState(false);
|
const [isSettingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||||
const [newFolderName, setNewFolderName] = useState('');
|
|
||||||
const [createFolderError, setCreateFolderError] = useState('');
|
|
||||||
const [creatingFolder, setCreatingFolder] = useState(false);
|
const [creatingFolder, setCreatingFolder] = useState(false);
|
||||||
const createFolderInputRef = useRef(null);
|
|
||||||
const [folderNodes, setFolderNodes] = useState(() => {
|
const [folderNodes, setFolderNodes] = useState(() => {
|
||||||
const rootNode = createRootNode();
|
const rootNode = createRootNode();
|
||||||
return new Map([[rootNode.id, rootNode]]);
|
return new Map([[rootNode.id, rootNode]]);
|
||||||
@@ -3852,7 +3848,7 @@ const AppLayout = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleFolderCreate = useCallback(
|
const handleFolderCreate = useCallback(
|
||||||
async (name, onSuccess) => {
|
async (name) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
setStatusMessage('Log in to create folders.', 'error');
|
setStatusMessage('Log in to create folders.', 'error');
|
||||||
return false;
|
return false;
|
||||||
@@ -3869,7 +3865,6 @@ const AppLayout = () => {
|
|||||||
let succeeded = false;
|
let succeeded = false;
|
||||||
try {
|
try {
|
||||||
const { data } = await api.post('/folders', payload);
|
const { data } = await api.post('/folders', payload);
|
||||||
onSuccess();
|
|
||||||
setStatusMessage('Folder created.', 'success');
|
setStatusMessage('Folder created.', 'success');
|
||||||
setFolderNodes((prev) => {
|
setFolderNodes((prev) => {
|
||||||
const next = new Map(prev);
|
const next = new Map(prev);
|
||||||
@@ -3895,6 +3890,7 @@ const AppLayout = () => {
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
await ensureFolderData(selectedFolder, { force: true, prefetchDepth: 1 });
|
await ensureFolderData(selectedFolder, { force: true, prefetchDepth: 1 });
|
||||||
|
await selectFolder(data.folder.id, { immediate: true });
|
||||||
succeeded = true;
|
succeeded = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error.response?.data?.error || 'Failed to create folder.';
|
const message = error.response?.data?.error || 'Failed to create folder.';
|
||||||
@@ -3905,23 +3901,32 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
return succeeded;
|
return succeeded;
|
||||||
},
|
},
|
||||||
[token, selectedFolder, ensureFolderData, notifyApiError, setStatusMessage],
|
[token, selectedFolder, ensureFolderData, notifyApiError, setStatusMessage, selectFolder],
|
||||||
);
|
);
|
||||||
|
|
||||||
const openCreateFolderModal = useCallback(() => {
|
const handlePromptCreateFolder = useCallback(async () => {
|
||||||
setNewFolderName('');
|
|
||||||
setCreateFolderError('');
|
|
||||||
setCreateFolderModalOpen(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const closeCreateFolderModal = useCallback(() => {
|
|
||||||
if (creatingFolder) {
|
if (creatingFolder) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCreateFolderModalOpen(false);
|
const input = window.prompt('New folder name');
|
||||||
setNewFolderName('');
|
if (!input) {
|
||||||
setCreateFolderError('');
|
return;
|
||||||
}, [creatingFolder]);
|
}
|
||||||
|
const trimmed = input.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
setStatusMessage('Folder name cannot be empty.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCreatingFolder(true);
|
||||||
|
try {
|
||||||
|
const success = await handleFolderCreate(trimmed);
|
||||||
|
if (!success) {
|
||||||
|
setStatusMessage('Unable to create folder. Check the status message for details.', 'error');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setCreatingFolder(false);
|
||||||
|
}
|
||||||
|
}, [creatingFolder, handleFolderCreate, setStatusMessage]);
|
||||||
|
|
||||||
const openTagsModal = useCallback(() => {
|
const openTagsModal = useCallback(() => {
|
||||||
setCorrespondentsModalOpen(false);
|
setCorrespondentsModalOpen(false);
|
||||||
@@ -3949,66 +3954,6 @@ const AppLayout = () => {
|
|||||||
setSettingsModalOpen(false);
|
setSettingsModalOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCreateFolderSubmit = useCallback(
|
|
||||||
async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const trimmed = newFolderName.trim();
|
|
||||||
if (!trimmed) {
|
|
||||||
setCreateFolderError('Folder name cannot be empty.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
setCreateFolderError('Log in to create folders.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCreatingFolder(true);
|
|
||||||
setCreateFolderError('');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const success = await handleFolderCreate(trimmed, () => {
|
|
||||||
setCreateFolderModalOpen(false);
|
|
||||||
setNewFolderName('');
|
|
||||||
setCreateFolderError('');
|
|
||||||
});
|
|
||||||
if (!success) {
|
|
||||||
setCreateFolderError('Unable to create folder. Check the status message for details.');
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setCreatingFolder(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[newFolderName, handleFolderCreate, token],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isCreateFolderModalOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const node = createFolderInputRef.current;
|
|
||||||
if (node) {
|
|
||||||
node.focus();
|
|
||||||
node.select();
|
|
||||||
}
|
|
||||||
}, [isCreateFolderModalOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isCreateFolderModalOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const handleKeyDown = (event) => {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
event.preventDefault();
|
|
||||||
closeCreateFolderModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [isCreateFolderModalOpen, closeCreateFolderModal]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTagsModalOpen(false);
|
setTagsModalOpen(false);
|
||||||
setCorrespondentsModalOpen(false);
|
setCorrespondentsModalOpen(false);
|
||||||
@@ -4985,8 +4930,6 @@ const AppLayout = () => {
|
|||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
onRefresh: refreshCurrentFolder,
|
onRefresh: refreshCurrentFolder,
|
||||||
onShowSkeuoWorkspace: showSkeuoWorkspace,
|
onShowSkeuoWorkspace: showSkeuoWorkspace,
|
||||||
onRequestCreateFolder: openCreateFolderModal,
|
|
||||||
creatingFolder,
|
|
||||||
subfolders: currentSubfolders,
|
subfolders: currentSubfolders,
|
||||||
documents,
|
documents,
|
||||||
searchResults,
|
searchResults,
|
||||||
@@ -5059,8 +5002,6 @@ const AppLayout = () => {
|
|||||||
handleFolderRename,
|
handleFolderRename,
|
||||||
handleFolderRowClick,
|
handleFolderRowClick,
|
||||||
isFilterActive,
|
isFilterActive,
|
||||||
creatingFolder,
|
|
||||||
openCreateFolderModal,
|
|
||||||
openDocumentPreview,
|
openDocumentPreview,
|
||||||
refreshCurrentFolder,
|
refreshCurrentFolder,
|
||||||
searchLoading,
|
searchLoading,
|
||||||
@@ -5092,6 +5033,8 @@ const AppLayout = () => {
|
|||||||
onFolderDragStart: handleFolderDragStart,
|
onFolderDragStart: handleFolderDragStart,
|
||||||
onFolderDragEnd: handleFolderDragEnd,
|
onFolderDragEnd: handleFolderDragEnd,
|
||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
|
onCreateFolder: handlePromptCreateFolder,
|
||||||
|
creatingFolder,
|
||||||
tags,
|
tags,
|
||||||
activeTagIds: activeTagFilters,
|
activeTagIds: activeTagFilters,
|
||||||
onToggleTagFilter: toggleTagFilter,
|
onToggleTagFilter: toggleTagFilter,
|
||||||
@@ -5148,6 +5091,8 @@ const AppLayout = () => {
|
|||||||
toggleTagFilter,
|
toggleTagFilter,
|
||||||
handleTagCreate,
|
handleTagCreate,
|
||||||
handleCorrespondentCreate,
|
handleCorrespondentCreate,
|
||||||
|
handlePromptCreateFolder,
|
||||||
|
creatingFolder,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5360,54 +5305,6 @@ const AppLayout = () => {
|
|||||||
folderName={dropOverlayState.folderName}
|
folderName={dropOverlayState.folderName}
|
||||||
/>
|
/>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
{isCreateFolderModalOpen && (
|
|
||||||
<div
|
|
||||||
className="modal-backdrop"
|
|
||||||
role="presentation"
|
|
||||||
onClick={closeCreateFolderModal}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="modal"
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true"
|
|
||||||
aria-labelledby="create-folder-title"
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
>
|
|
||||||
<h3 id="create-folder-title">Create folder</h3>
|
|
||||||
<form className="modal__form" onSubmit={handleCreateFolderSubmit}>
|
|
||||||
<label htmlFor="new-folder-name">Folder name</label>
|
|
||||||
<input
|
|
||||||
id="new-folder-name"
|
|
||||||
ref={createFolderInputRef}
|
|
||||||
value={newFolderName}
|
|
||||||
onChange={(event) => {
|
|
||||||
setNewFolderName(event.target.value);
|
|
||||||
if (createFolderError) {
|
|
||||||
setCreateFolderError('');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder="Enter folder name"
|
|
||||||
disabled={creatingFolder}
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
{createFolderError && <p className="modal__error">{createFolderError}</p>}
|
|
||||||
<div className="modal__actions">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="secondary"
|
|
||||||
onClick={closeCreateFolderModal}
|
|
||||||
disabled={creatingFolder}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button type="submit" disabled={creatingFolder}>
|
|
||||||
{creatingFolder ? 'Creating…' : 'Create'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isTagsModalOpen && (
|
{isTagsModalOpen && (
|
||||||
<div
|
<div
|
||||||
className="modal-backdrop"
|
className="modal-backdrop"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
|
FolderPlusIcon,
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
|
|
||||||
import { getTagColorStyle } from '../utils/colors';
|
import { getTagColorStyle } from '../utils/colors';
|
||||||
@@ -139,6 +140,8 @@ const Sidebar = ({
|
|||||||
onFolderDragStart,
|
onFolderDragStart,
|
||||||
onFolderDragEnd,
|
onFolderDragEnd,
|
||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
|
onCreateFolder,
|
||||||
|
creatingFolder = false,
|
||||||
tags = [],
|
tags = [],
|
||||||
activeTagIds = [],
|
activeTagIds = [],
|
||||||
onToggleTagFilter,
|
onToggleTagFilter,
|
||||||
@@ -209,6 +212,14 @@ const Sidebar = ({
|
|||||||
console.error('[sidebar] failed to create correspondent', error);
|
console.error('[sidebar] failed to create correspondent', error);
|
||||||
}
|
}
|
||||||
}, [onCreateCorrespondent]);
|
}, [onCreateCorrespondent]);
|
||||||
|
|
||||||
|
const handleCreateFolder = useCallback(() => {
|
||||||
|
if (creatingFolder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onCreateFolder?.();
|
||||||
|
}, [creatingFolder, onCreateFolder]);
|
||||||
|
|
||||||
const handleSearchInputChange = useCallback(
|
const handleSearchInputChange = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
onSearchChange?.(event.target.value);
|
onSearchChange?.(event.target.value);
|
||||||
@@ -442,6 +453,19 @@ const Sidebar = ({
|
|||||||
<div className="sidebar-section sidebar-section--folders">
|
<div className="sidebar-section sidebar-section--folders">
|
||||||
<div className="sidebar-section__header">
|
<div className="sidebar-section__header">
|
||||||
<h3>Folders</h3>
|
<h3>Folders</h3>
|
||||||
|
{onCreateFolder ? (
|
||||||
|
<div className="sidebar-section__actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="icon-button"
|
||||||
|
onClick={handleCreateFolder}
|
||||||
|
aria-label="Create folder"
|
||||||
|
disabled={creatingFolder}
|
||||||
|
>
|
||||||
|
<FolderPlusIcon size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<ul className="folder-tree">
|
<ul className="folder-tree">
|
||||||
{rootNode && renderNodes([rootNode.id], 0)}
|
{rootNode && renderNodes([rootNode.id], 0)}
|
||||||
|
|||||||
Reference in New Issue
Block a user