diff --git a/frontend/src/documents/DocumentsGrid.tsx b/frontend/src/documents/DocumentsGrid.tsx deleted file mode 100644 index c017e48..0000000 --- a/frontend/src/documents/DocumentsGrid.tsx +++ /dev/null @@ -1,441 +0,0 @@ -import React, { useMemo } from 'react'; -import { FolderIcon, CheckIcon, CloseIcon } from '../ui/icons'; -import DocumentThumbnailImage from './DocumentThumbnailImage'; -import CorrespondentLinks from './CorrespondentLinks'; -import { getTagColorStyle } from '../utils/colors'; -import { resolveCorrespondents } from './correspondents'; -import { writeTagTransferData, parseTagTransferPayload } from './tagTransfer'; -import useInlineRename from './useInlineRename'; -import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext'; - -import type { - Folder as FolderLike, - Document, -} from '../types/documents'; -import type { DocumentsViewProps } from './panel/DocumentsPanel'; - -interface DocumentsGridProps extends DocumentsViewProps { - gridIconSize?: number; -} - -const DocumentsGrid: React.FC = ({ - entries, - draggingDocumentIdsSet, - draggedFolderId, - onFolderClick, - onFolderSelect, - onFolderDragOver, - onFolderDragLeave, - onFolderDrop, - onFolderDragStart, - onFolderDragEnd, - onDocumentClick, - onDocumentActivate, - onDocumentDragStart, - onDocumentDragEnd, - onDocumentTagDragOver, - onDocumentTagDragLeave, - onDocumentTagDrop, - ensureAssetUrl, - getDocumentAsset, - gridIconSize, - tagLookupById, - onTagClick, - scrollRef, - onCorrespondentClick, - activeCorrespondentIdSet, - onDocumentRename, - onFolderRename, -}) => { - const { - selectedDocumentIds, - selectedFolderIds, - clearSelection, - } = useWorkspaceSelectionContext(); - const selectedDocumentIdsSet = useMemo(() => new Set(selectedDocumentIds), [selectedDocumentIds]); - const selectedFolderIdsSet = useMemo(() => new Set(selectedFolderIds || []), [selectedFolderIds]); - const { - editingId: editingDocumentId, - draftValue: documentDraft, - setDraftValue: setDocumentDraft, - beginEditing: beginDocumentEditing, - cancelEditing: cancelDocumentEditing, - submitEditing: submitDocumentEditing, - savingId: savingDocumentId, - attachInputRef: attachDocumentInputRef, - } = useInlineRename(onDocumentRename, { - getCurrentValue: (doc: Document) => doc?.title ?? '', - getEntityId: (doc: Document) => doc?.id ?? null, - }); - - const { - editingId: editingFolderId, - draftValue: folderDraft, - setDraftValue: setFolderDraft, - beginEditing: beginFolderEditing, - cancelEditing: cancelFolderEditing, - submitEditing: submitFolderEditing, - savingId: savingFolderId, - attachInputRef: attachFolderInputRef, - } = useInlineRename(onFolderRename, { - getCurrentValue: (folder: FolderLike) => folder?.name ?? '', - getEntityId: (folder: FolderLike) => folder?.id ?? null, - }); - - const documentSelectionCount = selectedDocumentIdsSet?.size ?? 0; - const folderSelectionCount = selectedFolderIdsSet?.size ?? 0; - const totalSelectionCount = documentSelectionCount + folderSelectionCount; - - return ( -
{ - if (event.target === event.currentTarget) { - clearSelection(); - } - }} - > - {entries.map((entry) => { - if (entry.type === 'folder') { - const folder = entry.folder; - if (!folder) { - return null; - } - const canDragFolder = folder.id !== 'root'; - const isDraggingFolder = draggedFolderId === folder.id; - const isSelectedFolder = selectedFolderIdsSet?.has(folder.id); - const classes = ['document-card', 'folder-card']; - if (isDraggingFolder) classes.push('is-dragging'); - if (isSelectedFolder) classes.push('selected'); - const canRenameFolder = Boolean(onFolderRename) && folder.id !== 'root'; - const isFolderEditing = editingFolderId === folder.id; - const folderDraftValue = isFolderEditing ? folderDraft : folder.name; - const trimmedFolderDraft = isFolderEditing ? folderDraft.trim() : ''; - const isFolderSaving = savingFolderId === folder.id; - const canSubmitFolder = - isFolderEditing && trimmedFolderDraft.length > 0 && trimmedFolderDraft !== folder.name; - const allowInlineFolderEdit = canRenameFolder && isSelectedFolder && totalSelectionCount === 1; - - return ( -
onFolderClick?.(folder, event)} - onDoubleClick={(event) => { - event.preventDefault(); - onFolderSelect?.(folder.id); - }} - onDragOver={(event) => onFolderDragOver?.(event, folder.id)} - onDragLeave={onFolderDragLeave} - onDrop={(event) => onFolderDrop?.(event, folder.id)} - onDragStart={(event) => { - if (canDragFolder) { - onFolderDragStart?.(event, folder.id); - } - }} - onDragEnd={(event) => { - if (canDragFolder) { - onFolderDragEnd?.(event); - } - }} - > -
- -
-
- {isFolderEditing ? ( -
- setFolderDraft(event.target.value)} - onClick={(event) => event.stopPropagation()} - onKeyDown={(event) => { - if (event.key === 'Enter') { - event.preventDefault(); - submitFolderEditing(folder); - } else if (event.key === 'Escape') { - event.preventDefault(); - cancelFolderEditing(event); - } - }} - onBlur={(event) => { - const nextFocus = event.relatedTarget; - if (!nextFocus || !event.currentTarget.parentElement?.contains(nextFocus)) { - cancelFolderEditing(); - } - }} - /> - - -
- ) : ( -
- { - if (!allowInlineFolderEdit) { - return; - } - event.preventDefault(); - event.stopPropagation(); - beginFolderEditing(folder); - }} - onKeyDown={(event) => { - if (!allowInlineFolderEdit) { - return; - } - if (event.key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - beginFolderEditing(folder); - } - }} - > - {folder.name} - -
- )} -
-
- ); - } - - const doc = entry.document; - if (!doc) { - return null; - } - - const isSelected = selectedDocumentIdsSet?.has(doc.id); - const isDraggingDoc = draggingDocumentIdsSet?.has(doc.id); - const tagList = Array.isArray(doc.tags) ? doc.tags : []; - const visibleTags = tagList.slice(0, 3); - const remainingTagCount = tagList.length > 3 ? tagList.length - 3 : 0; - const correspondents = resolveCorrespondents(doc); - const cardClasses = ['document-card', 'document']; - if (isSelected) cardClasses.push('selected'); - if (isDraggingDoc) cardClasses.push('is-dragging'); - const isEditingDoc = editingDocumentId === doc.id; - const documentDraftValue = isEditingDoc ? documentDraft : doc.title; - const trimmedDocumentDraft = isEditingDoc ? documentDraft.trim() : ''; - const isDocumentSaving = savingDocumentId === doc.id; - const canSubmitDocument = - isEditingDoc && trimmedDocumentDraft.length > 0 && trimmedDocumentDraft !== doc.title; - const allowInlineDocumentEdit = onDocumentRename && isSelected && totalSelectionCount === 1; - return ( -
onDocumentClick?.(doc, event)} - onDoubleClick={(event) => onDocumentActivate?.(doc, event)} - draggable - onDragStart={(event) => onDocumentDragStart?.(event, doc)} - onDragEnd={(event) => onDocumentDragEnd?.(event)} - onDragOver={(event) => onDocumentTagDragOver?.(event)} - onDragOverCapture={(event) => onDocumentTagDragOver?.(event)} - onDragLeave={onDocumentTagDragLeave} - onDragLeaveCapture={onDocumentTagDragLeave} - onDrop={(event) => { - event.preventDefault(); - event.stopPropagation(); - const payload = parseTagTransferPayload(event); - if (payload && onDocumentTagDrop) { - onDocumentTagDrop(doc.id, payload); - } - }} - - > - -
-
- {correspondents.length > 0 ? ( - - - - ) : null} - {isEditingDoc ? ( -
- setDocumentDraft(event.target.value)} - onClick={(event) => event.stopPropagation()} - onKeyDown={(event) => { - if (event.key === 'Enter') { - event.preventDefault(); - submitDocumentEditing(doc); - } else if (event.key === 'Escape') { - event.preventDefault(); - cancelDocumentEditing(event); - } - }} - onBlur={(event) => { - const nextFocus = event.relatedTarget; - if (!nextFocus || !event.currentTarget.parentElement?.contains(nextFocus)) { - cancelDocumentEditing(); - } - }} - /> - - -
- ) : ( -
- { - if (!allowInlineDocumentEdit) { - return; - } - event.preventDefault(); - event.stopPropagation(); - beginDocumentEditing(doc); - }} - onKeyDown={(event) => { - if (!allowInlineDocumentEdit) { - return; - } - if (event.key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - beginDocumentEditing(doc); - } - }} - > - {doc.title} - -
- )} -
- {visibleTags.length > 0 && ( -
- {visibleTags.map((tag, index) => { - const colorSource = tag?.color || tagLookupById?.get(tag.id)?.color; - const style = getTagColorStyle(colorSource); - const tagId = tag?.id ?? null; - const clickable = tagId != null && typeof onTagClick === 'function'; - const key = tagId ?? `${doc.id}-tag-${index}`; - return ( - { - event.stopPropagation(); - if (tagId == null) { - return; - } - onTagClick?.(tagId); - } : undefined} - draggable - onDragStart={(event) => { - event.stopPropagation(); - try { - if (event.dataTransfer) { - event.dataTransfer.effectAllowed = 'copyMove'; - } - } catch (error) { - console.warn('[documents] Failed to configure drag effect', error); - } - writeTagTransferData(event.dataTransfer, tag, doc.id); - }} - onDragEnd={(event) => { - event.stopPropagation(); - }} - onKeyDown={clickable ? (event) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - event.stopPropagation(); - if (tagId == null) { - return; - } - onTagClick?.(tagId); - } - } : undefined} - > - {tag.label} - - ); - })} - {remainingTagCount > 0 && ( - +{remainingTagCount} - )} -
- )} -
-
- ); - })} -
- ); -}; - -export default DocumentsGrid; diff --git a/frontend/src/documents/DocumentsList.tsx b/frontend/src/documents/DocumentsList.tsx deleted file mode 100644 index a1f5177..0000000 --- a/frontend/src/documents/DocumentsList.tsx +++ /dev/null @@ -1,449 +0,0 @@ -import React, { useMemo } from 'react'; -import { FolderIcon, CheckIcon, CloseIcon } from '../ui/icons'; -import { getTagColorStyle } from '../utils/colors'; -import { formatDate } from '../utils/date'; -import DocumentThumbnailImage from './DocumentThumbnailImage'; -import CorrespondentLinks from './CorrespondentLinks'; -import { resolveCorrespondents } from './correspondents'; -import { writeTagTransferData, parseTagTransferPayload } from './tagTransfer'; -import useInlineRename from './useInlineRename'; -import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext'; - -import type { DocumentsViewProps } from './panel/DocumentsPanel'; -import type { - Document, - Folder, -} from '../types/documents'; - -const DocumentsList: React.FC = ({ - entries, - draggingDocumentIdsSet, - draggedFolderId, - ensureAssetUrl, - getDocumentAsset, - onFolderClick, - onFolderSelect, - onFolderDragOver, - onFolderDragLeave, - onFolderDrop, - onFolderDragStart, - onFolderDragEnd, - onFolderRename, - onDocumentClick, - onDocumentActivate, - onDocumentDragStart, - onDocumentDragEnd, - onDocumentTagDragOver, - onDocumentTagDragLeave, - onDocumentTagDrop, - onDocumentRename, - tagLookupById, - onTagClick, - onCorrespondentClick, - activeCorrespondentIdSet, - scrollRef, -}) => { - const { - selectedDocumentIds, - selectedFolderIds, - clearSelection, - } = useWorkspaceSelectionContext(); - const selectedDocumentIdsSet = useMemo(() => new Set(selectedDocumentIds), [selectedDocumentIds]); - const selectedFolderIdsSet = useMemo( - () => new Set(selectedFolderIds || []), - [selectedFolderIds], - ); - const { - editingId: editingDocumentId, - draftValue: documentDraft, - setDraftValue: setDocumentDraft, - beginEditing: beginDocumentEditing, - cancelEditing: cancelDocumentEditing, - submitEditing: submitDocumentEditing, - savingId: savingDocumentId, - attachInputRef: attachDocumentInputRef, - } = useInlineRename(onDocumentRename, { - getCurrentValue: (doc: Document) => doc?.title ?? '', - getEntityId: (doc: Document) => doc?.id ?? null, - }); - - const { - editingId: editingFolderId, - draftValue: folderDraft, - setDraftValue: setFolderDraft, - beginEditing: beginFolderEditing, - cancelEditing: cancelFolderEditing, - submitEditing: submitFolderEditing, - savingId: savingFolderId, - attachInputRef: attachFolderInputRef, - } = useInlineRename(onFolderRename, { - getCurrentValue: (folder: Folder) => folder?.name ?? '', - getEntityId: (folder: Folder) => folder?.id ?? null, - }); - - - const documentSelectionCount = selectedDocumentIdsSet?.size ?? 0; - const folderSelectionCount = selectedFolderIdsSet?.size ?? 0; - const totalSelectionCount = documentSelectionCount + folderSelectionCount; - - return ( - - { - clearSelection(); - }} - > - - - - - - - - - {entries.map((entry) => { - if (entry.type === 'folder') { - const folder = entry.folder; - if (!folder) { - return null; - } - const canDragFolder = folder.id !== 'root'; - const isDraggingFolder = draggedFolderId === folder.id; - const isSelectedFolder = selectedFolderIdsSet?.has(folder.id); - const canRenameFolder = Boolean(onFolderRename) && folder.id !== 'root'; - const isFolderEditing = editingFolderId === folder.id; - const folderDraftValue = isFolderEditing ? folderDraft : folder.name; - const trimmedFolderDraft = isFolderEditing ? folderDraft.trim() : ''; - const isFolderSaving = savingFolderId === folder.id; - const canSubmitFolder = - isFolderEditing && trimmedFolderDraft.length > 0 && trimmedFolderDraft !== folder.name; - const allowInlineFolderEdit = canRenameFolder && isSelectedFolder && totalSelectionCount === 1; - - return ( - onFolderClick?.(folder, event)} - onDoubleClick={(event) => { - event.preventDefault(); - onFolderSelect?.(folder.id); - }} - onDragOver={(event) => onFolderDragOver?.(event, folder.id)} - onDragLeave={onFolderDragLeave} - onDrop={(event) => onFolderDrop?.(event, folder.id)} - draggable={canDragFolder} - onDragStart={(event) => { - if (canDragFolder) { - onFolderDragStart?.(event, folder.id); - } - }} - onDragEnd={(event) => { - if (canDragFolder) { - onFolderDragEnd?.(event); - } - }} - > - - - - - - ); - } - - const doc = entry.document; - if (!doc) { - return null; - } - const isSelected = selectedDocumentIdsSet?.has(doc.id); - const isDraggingDoc = draggingDocumentIdsSet?.has(doc.id); - const rowClasses = ['document']; - if (isSelected) rowClasses.push('selected'); - if (isDraggingDoc) rowClasses.push('is-dragging'); - const correspondents = resolveCorrespondents(doc); - const isEditingDoc = editingDocumentId === doc.id; - const documentDraftValue = isEditingDoc ? documentDraft : doc.title; - const trimmedDocumentDraft = isEditingDoc ? documentDraft.trim() : ''; - const isDocumentSaving = savingDocumentId === doc.id; - const canSubmitDocument = - isEditingDoc && trimmedDocumentDraft.length > 0 && trimmedDocumentDraft !== doc.title; - const allowInlineDocumentEdit = - onDocumentRename && isSelected && totalSelectionCount === 1; - const issuedLabel = formatDate(doc.issued_at); - const addedLabel = formatDate(doc.created_at || doc.uploaded_at); - return ( - onDocumentClick?.(doc, event)} - onDoubleClick={(event) => onDocumentActivate?.(doc, event)} - onDragStart={(event) => onDocumentDragStart?.(event, doc)} - onDragEnd={(event) => onDocumentDragEnd?.(event)} - onDragOver={onDocumentTagDragOver} - onDragLeave={onDocumentTagDragLeave} - onDrop={(event) => { - event.preventDefault(); - event.stopPropagation(); - const payload = parseTagTransferPayload(event); - if (payload && onDocumentTagDrop) { - onDocumentTagDrop(doc.id, payload); - } - }} - > - - - - - - ); - })} - -
 NameIssuedAdded
-
- -
-
-
- - - {isFolderEditing ? ( - - setFolderDraft(event.target.value)} - onClick={(event) => event.stopPropagation()} - onKeyDown={(event) => { - if (event.key === 'Enter') { - event.preventDefault(); - submitFolderEditing(folder); - } else if (event.key === 'Escape') { - event.preventDefault(); - cancelFolderEditing(event); - } - }} - onBlur={(event) => { - const nextFocus = event.relatedTarget; - if (!nextFocus || !event.currentTarget.parentElement?.contains(nextFocus)) { - cancelFolderEditing(); - } - }} - /> - - - - ) : ( - { - if (!allowInlineFolderEdit) { - return; - } - event.preventDefault(); - event.stopPropagation(); - beginFolderEditing(folder); - }} - onKeyDown={(event) => { - if (!allowInlineFolderEdit) { - return; - } - if (event.key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - beginFolderEditing(folder); - } - }} - > - {folder.name} - - )} - - -
-
- - -
-
- - {correspondents.length > 0 ? ( - - - - ) : null} - - {isEditingDoc ? ( - - setDocumentDraft(event.target.value)} - onClick={(event) => event.stopPropagation()} - onKeyDown={(event) => { - if (event.key === 'Enter') { - event.preventDefault(); - submitDocumentEditing(doc); - } else if (event.key === 'Escape') { - event.preventDefault(); - cancelDocumentEditing(event); - } - }} - onBlur={(event) => { - const nextFocus = event.relatedTarget; - if (!nextFocus || !event.currentTarget.parentElement?.contains(nextFocus)) { - cancelDocumentEditing(); - } - }} - /> - - - - ) : ( - { - if (!allowInlineDocumentEdit) { - return; - } - event.preventDefault(); - event.stopPropagation(); - beginDocumentEditing(doc); - }} - onKeyDown={(event) => { - if (!allowInlineDocumentEdit) { - return; - } - if (event.key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - beginDocumentEditing(doc); - } - }} - > - {doc.title} - - )} - - -
- {(doc.tags || []).length > 0 && ( -
- {(doc.tags || []).map((tag, index) => { - const colorSource = tag?.color || tagLookupById?.get(tag.id)?.color; - const style = getTagColorStyle(colorSource); - const tagId = tag?.id ?? null; - const clickable = tagId != null && typeof onTagClick === 'function'; - const key = tagId ?? `${doc.id}-tag-${index}`; - return ( - { - event.stopPropagation(); - if (tagId == null) return; - onTagClick?.(tagId); - } : undefined} - draggable - onDragStart={(event) => { - event.stopPropagation(); - try { - if (event.dataTransfer) { - event.dataTransfer.effectAllowed = 'copyMove'; - } - } catch (error) { - console.warn('[documents] Failed to configure drag effect', error); - } - writeTagTransferData(event.dataTransfer, tag, doc.id); - }} - onDragEnd={(event) => { - event.stopPropagation(); - }} - onKeyDown={clickable ? (event) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - event.stopPropagation(); - if (tagId == null) { - return; - } - onTagClick?.(tagId); - } - } : undefined} - > - {tag.label} - - ); - })} -
- )} -
-
{issuedLabel}{addedLabel}
- ); -}; - -export default DocumentsList; diff --git a/frontend/src/documents/DocumentsView.tsx b/frontend/src/documents/DocumentsView.tsx new file mode 100644 index 0000000..2679b10 --- /dev/null +++ b/frontend/src/documents/DocumentsView.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { useDocumentViewLogic, DocumentViewLogic } from './hooks/useDocumentViewLogic'; +import DocumentsListRow from './components/DocumentsListRow'; +import DocumentsGridCard from './components/DocumentsGridCard'; +import DocumentsListContainer from './components/DocumentsListContainer'; +import DocumentsGridContainer from './components/DocumentsGridContainer'; +import type { DocumentsViewProps } from './panel/DocumentsPanel'; + +interface AbstractDocumentsViewProps void; children: React.ReactNode }> extends DocumentsViewProps { + ContainerComponent: React.ComponentType; + ItemComponent: React.ComponentType<{ entry: any; viewLogic: DocumentViewLogic } & DocumentsViewProps>; + containerProps?: Omit; + [key: string]: any; +} + +const AbstractDocumentsView = void; children: React.ReactNode }>({ + ContainerComponent, + ItemComponent, + containerProps, + ...props +}: AbstractDocumentsViewProps) => { + const { entries, onDocumentRename, onFolderRename } = props; + const viewLogic = useDocumentViewLogic({ + onDocumentRename, + onFolderRename, + }); + const { clearSelection } = viewLogic; + + return ( + + {entries.map((entry) => ( + + ))} + + ); +}; + +export const DocumentsList: React.FC = (props) => { + return ( + + ); +}; + +export const DocumentsGrid: React.FC = (props) => { + return ( + + ); +}; diff --git a/frontend/src/documents/components/DocumentEntry.tsx b/frontend/src/documents/components/DocumentEntry.tsx new file mode 100644 index 0000000..82214f6 --- /dev/null +++ b/frontend/src/documents/components/DocumentEntry.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import type { DocumentsViewProps } from '../panel/DocumentsPanel'; +import type { DocumentViewLogic } from '../hooks/useDocumentViewLogic'; +import { useDocumentItemLogic } from '../hooks/useDocumentItemLogic'; +import EntryShell from './EntryShell'; + +interface DocumentEntryProps extends DocumentsViewProps { + doc: any; + viewLogic: DocumentViewLogic; + component: React.ElementType; + className?: string; + role?: string; + children: (logic: ReturnType) => React.ReactNode; +} + +const DocumentEntry: React.FC = (props) => { + const { doc, component, className, role, children, viewLogic } = props; + const logic = useDocumentItemLogic({ doc, viewLogic, ...props }); + + return ( + + {children(logic)} + + ); +}; + +export default DocumentEntry; diff --git a/frontend/src/documents/components/DocumentTags.tsx b/frontend/src/documents/components/DocumentTags.tsx new file mode 100644 index 0000000..8a33906 --- /dev/null +++ b/frontend/src/documents/components/DocumentTags.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { getTagColorStyle } from '../../utils/colors'; +import { writeTagTransferData } from '../tagTransfer'; +import type { DocumentTag } from '../../types/documents'; +import type { Identifier } from '../../types/identifiers'; + +interface DocumentTagsProps { + tags: DocumentTag[]; + tagLookupById?: Map | null; + onTagClick?: (tagId: Identifier) => void; + docId: Identifier; + maxTags?: number; +} + +const DocumentTags: React.FC = ({ + tags, + tagLookupById, + onTagClick, + docId, + maxTags, +}) => { + const visibleTags = maxTags ? tags.slice(0, maxTags) : tags; + const remainingTagCount = maxTags && tags.length > maxTags ? tags.length - maxTags : 0; + + if (tags.length === 0) { + return null; + } + + return ( + <> + {visibleTags.map((tag, index) => { + const colorSource = tag?.color || tagLookupById?.get(tag.id)?.color; + const style = getTagColorStyle(colorSource); + const tagId = tag?.id ?? null; + const clickable = tagId != null && typeof onTagClick === 'function'; + const key = tagId ?? `${docId}-tag-${index}`; + + return ( + { + event.stopPropagation(); + if (tagId == null) return; + onTagClick?.(tagId); + } : undefined} + draggable + onDragStart={(event) => { + event.stopPropagation(); + try { + if (event.dataTransfer) { + event.dataTransfer.effectAllowed = 'copyMove'; + } + } catch (error) { + console.warn('[documents] Failed to configure drag effect', error); + } + writeTagTransferData(event.dataTransfer, tag, docId); + }} + onDragEnd={(event) => { + event.stopPropagation(); + }} + onKeyDown={clickable ? (event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + event.stopPropagation(); + if (tagId == null) return; + onTagClick?.(tagId); + } + } : undefined} + > + {tag.label} + + ); + })} + {remainingTagCount > 0 && ( + +{remainingTagCount} + )} + + ); +}; + +export default DocumentTags; diff --git a/frontend/src/documents/components/DocumentsGridCard.tsx b/frontend/src/documents/components/DocumentsGridCard.tsx new file mode 100644 index 0000000..03fd424 --- /dev/null +++ b/frontend/src/documents/components/DocumentsGridCard.tsx @@ -0,0 +1,129 @@ +import React from 'react'; +import { FolderIcon } from '../../ui/icons'; +import DocumentThumbnailImage from '../DocumentThumbnailImage'; +import { resolveCorrespondents } from '../correspondents'; +import type { DocumentsListEntry } from '../../types/documents'; +import type { DocumentsViewProps } from '../panel/DocumentsPanel'; +import type { DocumentViewLogic } from '../hooks/useDocumentViewLogic'; +import EditableEntryTitle from './EditableEntryTitle'; +import EntryCorrespondents from './EntryCorrespondents'; +import EntryTags from './EntryTags'; +import FolderEntry from './FolderEntry'; +import DocumentEntry from './DocumentEntry'; + +interface DocumentsGridCardProps extends DocumentsViewProps { + entry: DocumentsListEntry; + viewLogic: DocumentViewLogic; + gridIconSize?: number; +} + +const DocumentsGridCard: React.FC = (props) => { + const { entry, gridIconSize } = props; + + if (entry.type === 'folder') { + const folder = entry.folder; + if (!folder) return null; + + return ( + + {(logic) => ( + <> +
+ +
+
+
+ + {folder.name} + +
+
+ + )} +
+ ); + } + + const doc = entry.document; + if (!doc) return null; + + const correspondents = resolveCorrespondents(doc); + + return ( + + {(logic) => ( + <> + +
+
+ +
+ + {doc.title} + +
+
+
+ +
+
+ + )} +
+ ); +}; + +export default DocumentsGridCard; diff --git a/frontend/src/documents/components/DocumentsGridContainer.tsx b/frontend/src/documents/components/DocumentsGridContainer.tsx new file mode 100644 index 0000000..3c54c1b --- /dev/null +++ b/frontend/src/documents/components/DocumentsGridContainer.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +interface DocumentsGridContainerProps { + children: React.ReactNode; + clearSelection: () => void; + gridIconSize?: number; +} + +const DocumentsGridContainer: React.FC = ({ + children, + clearSelection, + gridIconSize, +}) => { + return ( +
{ + if (event.target === event.currentTarget) { + clearSelection(); + } + }} + > + {children} +
+ ); +}; + +export default DocumentsGridContainer; diff --git a/frontend/src/documents/components/DocumentsListContainer.tsx b/frontend/src/documents/components/DocumentsListContainer.tsx new file mode 100644 index 0000000..929fbc3 --- /dev/null +++ b/frontend/src/documents/components/DocumentsListContainer.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +interface DocumentsListContainerProps { + children: React.ReactNode; + clearSelection: () => void; +} + +const DocumentsListContainer: React.FC = ({ + children, + clearSelection, +}) => { + return ( + + { + clearSelection(); + }} + > + + + + + + + + {children} +
 NameIssuedAdded
+ ); +}; + +export default DocumentsListContainer; diff --git a/frontend/src/documents/components/DocumentsListRow.tsx b/frontend/src/documents/components/DocumentsListRow.tsx new file mode 100644 index 0000000..e565812 --- /dev/null +++ b/frontend/src/documents/components/DocumentsListRow.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { FolderIcon } from '../../ui/icons'; +import { formatDate } from '../../utils/date'; +import DocumentThumbnailImage from '../DocumentThumbnailImage'; +import { resolveCorrespondents } from '../correspondents'; +import type { DocumentsListEntry } from '../../types/documents'; +import type { DocumentsViewProps } from '../panel/DocumentsPanel'; +import type { DocumentViewLogic } from '../hooks/useDocumentViewLogic'; +import EditableEntryTitle from './EditableEntryTitle'; +import EntryCorrespondents from './EntryCorrespondents'; +import EntryTags from './EntryTags'; +import FolderEntry from './FolderEntry'; +import DocumentEntry from './DocumentEntry'; + +interface DocumentsListRowProps extends DocumentsViewProps { + entry: DocumentsListEntry; + viewLogic: DocumentViewLogic; +} + +const DocumentsListRow: React.FC = (props) => { + const { entry } = props; + + if (entry.type === 'folder') { + const folder = entry.folder; + if (!folder) return null; + + return ( + + {(logic) => ( + <> + +
+ +
+ + +
+ + + + {folder.name} + + + +
+ + — + — + + )} +
+ ); + } + + const doc = entry.document; + if (!doc) return null; + + const correspondents = resolveCorrespondents(doc); + const issuedLabel = formatDate(doc.issued_at); + const addedLabel = formatDate(doc.created_at || doc.uploaded_at); + + return ( + + {(logic) => ( + <> + + + + +
+
+ + + + + {doc.title} + + + +
+
+ +
+
+ + {issuedLabel} + {addedLabel} + + )} +
+ ); +}; + +export default DocumentsListRow; diff --git a/frontend/src/documents/components/EditableEntryTitle.tsx b/frontend/src/documents/components/EditableEntryTitle.tsx new file mode 100644 index 0000000..1ff3e75 --- /dev/null +++ b/frontend/src/documents/components/EditableEntryTitle.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import InlineRenameInput from './InlineRenameInput'; + +interface EditableEntryTitleProps { + isEditing: boolean; + draftValue: string; + onChange: (value: string) => void; + onSubmit: () => void; + onCancel: (event?: React.SyntheticEvent) => void; + isSaving: boolean; + canSubmit: boolean; + inputRef: (ref: HTMLInputElement | null) => void; + allowInlineEdit: boolean | undefined; + onBeginEditing: (event: React.SyntheticEvent) => void; + children: React.ReactNode; + className?: string; +} + +const EditableEntryTitle: React.FC = ({ + isEditing, + draftValue, + onChange, + onSubmit, + onCancel, + isSaving, + canSubmit, + inputRef, + allowInlineEdit, + onBeginEditing, + children, + className, +}) => { + if (isEditing) { + return ( +
+ +
+ ); + } + + return ( + { + if (!allowInlineEdit) return; + if (event.key === 'Enter') { + onBeginEditing(event); + } + }} + > + {children} + + ); +}; + +export default EditableEntryTitle; diff --git a/frontend/src/documents/components/EntryCorrespondents.tsx b/frontend/src/documents/components/EntryCorrespondents.tsx new file mode 100644 index 0000000..77b91bb --- /dev/null +++ b/frontend/src/documents/components/EntryCorrespondents.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import CorrespondentLinks from '../CorrespondentLinks'; +import type { Identifier } from '../../types/identifiers'; + +interface EntryCorrespondentsProps { + correspondents: any[]; + activeCorrespondentIdSet?: Set | null; + onCorrespondentClick?: (correspondentId: Identifier) => void; +} + +const EntryCorrespondents: React.FC = (props) => { + if (!props.correspondents || props.correspondents.length === 0) { + return null; + } + + return ( + + + + ); +}; + +export default EntryCorrespondents; diff --git a/frontend/src/documents/components/EntryShell.tsx b/frontend/src/documents/components/EntryShell.tsx new file mode 100644 index 0000000..027c47b --- /dev/null +++ b/frontend/src/documents/components/EntryShell.tsx @@ -0,0 +1,66 @@ +import React, { type DragEvent } from 'react'; + +export interface EntryShellHandlers { + onClick: (event: React.MouseEvent) => void; + onDoubleClick: (event: React.MouseEvent) => void; + onDragStart: (event: DragEvent) => void; + onDragEnd: (event: DragEvent) => void; + onDragOver: (event: DragEvent) => void; + onDragLeave: (event: DragEvent) => void; + onDrop: (event: DragEvent) => void; + onDragOverCapture?: (event: DragEvent) => void; + onDragLeaveCapture?: (event: DragEvent) => void; +} + +interface EntryShellProps { + component: React.ElementType; + handlers: EntryShellHandlers; + isSelected?: boolean; + isDragging?: boolean; + canDrag?: boolean; + id: string; + className?: string; + children: React.ReactNode; + docId?: number; + role?: string; +} + +const EntryShell: React.FC = ({ + component: Component, + handlers, + isSelected, + isDragging, + canDrag, + id, + className = '', + children, + docId, + role, +}) => { + const classes = [className]; + if (isSelected) classes.push('selected'); + if (isDragging) classes.push('is-dragging'); + + const commonProps = { + id, + className: classes.join(' '), + onClick: handlers.onClick, + onDoubleClick: handlers.onDoubleClick, + draggable: canDrag, + onDragStart: handlers.onDragStart, + onDragEnd: handlers.onDragEnd, + onDragOver: handlers.onDragOver, + onDragLeave: handlers.onDragLeave, + onDrop: handlers.onDrop, + ...(docId ? { 'data-doc-id': docId } : {}), + ...(role ? { role } : {}), + }; + + return ( + + {children} + + ); +}; + +export default EntryShell; diff --git a/frontend/src/documents/components/EntryTags.tsx b/frontend/src/documents/components/EntryTags.tsx new file mode 100644 index 0000000..9dcdf97 --- /dev/null +++ b/frontend/src/documents/components/EntryTags.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import DocumentTags from './DocumentTags'; +import type { Identifier } from '../../types/identifiers'; +import type { DocumentTag } from '../../types/documents'; + +interface EntryTagsProps { + tags: Identifier[]; + tagLookupById?: Map | null; + onTagClick?: (tagId: Identifier) => void; + docId: Identifier; + maxTags?: number; +} + +const EntryTags: React.FC = (props) => { + if (!props.tags || props.tags.length === 0) { + return null; + } + + return ( + + ); +}; + +export default EntryTags; diff --git a/frontend/src/documents/components/FolderEntry.tsx b/frontend/src/documents/components/FolderEntry.tsx new file mode 100644 index 0000000..9353c5f --- /dev/null +++ b/frontend/src/documents/components/FolderEntry.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import type { DocumentsViewProps } from '../panel/DocumentsPanel'; +import type { DocumentViewLogic } from '../hooks/useDocumentViewLogic'; +import { useFolderItemLogic } from '../hooks/useFolderItemLogic'; +import EntryShell from './EntryShell'; + +interface FolderEntryProps extends DocumentsViewProps { + folder: any; + viewLogic: DocumentViewLogic; + component: React.ElementType; + className?: string; + role?: string; + children: (logic: ReturnType) => React.ReactNode; +} + +const FolderEntry: React.FC = (props) => { + const { folder, component, className, role, children, viewLogic } = props; + const logic = useFolderItemLogic({ folder, viewLogic, ...props }); + + return ( + + {children(logic)} + + ); +}; + +export default FolderEntry; diff --git a/frontend/src/documents/components/InlineRenameInput.tsx b/frontend/src/documents/components/InlineRenameInput.tsx new file mode 100644 index 0000000..839546f --- /dev/null +++ b/frontend/src/documents/components/InlineRenameInput.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { CheckIcon, CloseIcon } from '../../ui/icons'; + +interface InlineRenameInputProps { + value: string; + onChange: (value: string) => void; + onSubmit: () => void; + onCancel: (event?: React.SyntheticEvent) => void; + isSaving?: boolean; + canSubmit?: boolean; + inputRef?: React.Ref; + className?: string; +} + +const InlineRenameInput: React.FC = ({ + value, + onChange, + onSubmit, + onCancel, + isSaving = false, + canSubmit = true, + inputRef, + className = 'doc-title-edit', +}) => { + return ( + + onChange(event.target.value)} + onClick={(event) => event.stopPropagation()} + onKeyDown={(event) => { + if (event.key === 'Enter') { + event.preventDefault(); + onSubmit(); + } else if (event.key === 'Escape') { + event.preventDefault(); + onCancel(event); + } + }} + onBlur={(event) => { + const nextFocus = event.relatedTarget; + if (!nextFocus || !event.currentTarget.parentElement?.contains(nextFocus)) { + onCancel(); + } + }} + /> + + + + ); +}; + +export default InlineRenameInput; diff --git a/frontend/src/documents/hooks/useDocumentItemLogic.ts b/frontend/src/documents/hooks/useDocumentItemLogic.ts new file mode 100644 index 0000000..2eb46c4 --- /dev/null +++ b/frontend/src/documents/hooks/useDocumentItemLogic.ts @@ -0,0 +1,89 @@ +import React, { type DragEvent } from 'react'; +import { parseTagTransferPayload } from '../tagTransfer'; +import type { Document } from '../../types/documents'; +import type { DocumentsViewProps } from '../panel/DocumentsPanel'; +import type { DocumentViewLogic } from './useDocumentViewLogic'; + +interface UseDocumentItemLogicProps extends DocumentsViewProps { + doc: Document; + viewLogic: DocumentViewLogic; +} + +export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => { + const { + doc, + viewLogic, + draggingDocumentIdsSet, + onDocumentClick, + onDocumentActivate, + onDocumentDragStart, + onDocumentDragEnd, + onDocumentTagDragOver, + onDocumentTagDragLeave, + onDocumentTagDrop, + onDocumentRename, + } = props; + + const { + selectedDocumentIdsSet, + totalSelectionCount, + documentRename: { + editingId: editingDocumentId, + draftValue: documentDraft, + setDraftValue: setDocumentDraft, + beginEditing: beginDocumentEditing, + cancelEditing: cancelDocumentEditing, + submitEditing: submitDocumentEditing, + savingId: savingDocumentId, + attachInputRef: attachDocumentInputRef, + }, + } = viewLogic; + + const isSelected = selectedDocumentIdsSet?.has(doc.id); + const isDraggingDoc = draggingDocumentIdsSet?.has(doc.id); + const isEditingDoc = editingDocumentId === doc.id; + const documentDraftValue = isEditingDoc ? documentDraft : doc.title; + const trimmedDocumentDraft = isEditingDoc ? documentDraft.trim() : ''; + const isDocumentSaving = savingDocumentId === doc.id; + const canSubmitDocument = + isEditingDoc && trimmedDocumentDraft.length > 0 && trimmedDocumentDraft !== doc.title; + const allowInlineDocumentEdit = onDocumentRename && isSelected && totalSelectionCount === 1; + + const handlers = { + onClick: (event: React.MouseEvent) => onDocumentClick?.(doc, event), + onDoubleClick: (event: React.MouseEvent) => onDocumentActivate?.(doc, event), + onDragStart: (event: DragEvent) => onDocumentDragStart?.(event, doc), + onDragEnd: (event: DragEvent) => onDocumentDragEnd?.(event), + onDragOver: (event: DragEvent) => onDocumentTagDragOver?.(event), + onDragLeave: onDocumentTagDragLeave, + onDrop: (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + const payload = parseTagTransferPayload(event); + if (payload && onDocumentTagDrop) { + onDocumentTagDrop(doc.id, payload); + } + }, + onRenameChange: setDocumentDraft, + onRenameSubmit: () => submitDocumentEditing(doc), + onRenameCancel: (event?: React.SyntheticEvent) => cancelDocumentEditing(event), + onRenameBegin: (event: React.SyntheticEvent) => { + if (!allowInlineDocumentEdit) return; + event.preventDefault(); + event.stopPropagation(); + beginDocumentEditing(doc); + }, + }; + + return { + isSelected, + isDraggingDoc, + isEditingDoc, + documentDraftValue, + isDocumentSaving, + canSubmitDocument, + allowInlineDocumentEdit, + attachDocumentInputRef, + handlers, + }; +}; diff --git a/frontend/src/documents/hooks/useDocumentViewLogic.ts b/frontend/src/documents/hooks/useDocumentViewLogic.ts new file mode 100644 index 0000000..a4a9a99 --- /dev/null +++ b/frontend/src/documents/hooks/useDocumentViewLogic.ts @@ -0,0 +1,51 @@ +import { useMemo } from 'react'; +import { useWorkspaceSelectionContext } from '../../app/WorkspaceSelectionContext'; +import useInlineRename from '../useInlineRename'; +import type { Document, Folder } from '../../types/documents'; + +interface UseDocumentViewLogicProps { + onDocumentRename?: (id: string, name: string) => Promise | boolean; + onFolderRename?: (id: string, name: string) => Promise | boolean; +} + +export const useDocumentViewLogic = ({ + onDocumentRename, + onFolderRename, +}: UseDocumentViewLogicProps) => { + const { + selectedDocumentIds, + selectedFolderIds, + clearSelection, + } = useWorkspaceSelectionContext(); + + const selectedDocumentIdsSet = useMemo(() => new Set(selectedDocumentIds), [selectedDocumentIds]); + const selectedFolderIdsSet = useMemo( + () => new Set(selectedFolderIds || []), + [selectedFolderIds], + ); + + const documentSelectionCount = selectedDocumentIdsSet.size; + const folderSelectionCount = selectedFolderIdsSet.size; + const totalSelectionCount = documentSelectionCount + folderSelectionCount; + + const documentRename = useInlineRename(onDocumentRename, { + getCurrentValue: (doc: Document) => doc?.title ?? '', + getEntityId: (doc: Document) => doc?.id ?? null, + }); + + const folderRename = useInlineRename(onFolderRename, { + getCurrentValue: (folder: Folder) => folder?.name ?? '', + getEntityId: (folder: Folder) => folder?.id ?? null, + }); + + return { + selectedDocumentIdsSet, + selectedFolderIdsSet, + clearSelection, + totalSelectionCount, + documentRename, + folderRename, + }; +}; + +export type DocumentViewLogic = ReturnType; diff --git a/frontend/src/documents/hooks/useFolderItemLogic.ts b/frontend/src/documents/hooks/useFolderItemLogic.ts new file mode 100644 index 0000000..5563992 --- /dev/null +++ b/frontend/src/documents/hooks/useFolderItemLogic.ts @@ -0,0 +1,94 @@ +import React, { type DragEvent } from 'react'; +import type { DocumentsViewProps } from '../panel/DocumentsPanel'; +import type { DocumentViewLogic } from './useDocumentViewLogic'; + +interface UseFolderItemLogicProps extends DocumentsViewProps { + folder: any; + viewLogic: DocumentViewLogic; +} + +export const useFolderItemLogic = (props: UseFolderItemLogicProps) => { + const { + folder, + viewLogic, + draggedFolderId, + onFolderClick, + onFolderSelect, + onFolderDragOver, + onFolderDragLeave, + onFolderDrop, + onFolderDragStart, + onFolderDragEnd, + onFolderRename, + } = props; + + const { + selectedFolderIdsSet, + totalSelectionCount, + folderRename: { + editingId: editingFolderId, + draftValue: folderDraft, + setDraftValue: setFolderDraft, + beginEditing: beginFolderEditing, + cancelEditing: cancelFolderEditing, + submitEditing: submitFolderEditing, + savingId: savingFolderId, + attachInputRef: attachFolderInputRef, + }, + } = viewLogic; + + const canDragFolder = folder.id !== 'root'; + const isDraggingFolder = draggedFolderId === folder.id; + const isSelectedFolder = selectedFolderIdsSet?.has(folder.id); + const canRenameFolder = Boolean(onFolderRename) && folder.id !== 'root'; + const isFolderEditing = editingFolderId === folder.id; + const folderDraftValue = isFolderEditing ? folderDraft : folder.name; + const trimmedFolderDraft = isFolderEditing ? folderDraft.trim() : ''; + const isFolderSaving = savingFolderId === folder.id; + const canSubmitFolder = + isFolderEditing && trimmedFolderDraft.length > 0 && trimmedFolderDraft !== folder.name; + const allowInlineFolderEdit = canRenameFolder && isSelectedFolder && totalSelectionCount === 1; + + const handlers = { + onClick: (event: React.MouseEvent) => onFolderClick?.(folder, event), + onDoubleClick: (event: React.MouseEvent) => { + event.preventDefault(); + onFolderSelect?.(folder.id); + }, + onDragOver: (event: DragEvent) => onFolderDragOver?.(event, folder.id), + onDragLeave: onFolderDragLeave, + onDrop: (event: DragEvent) => onFolderDrop?.(event, folder.id), + onDragStart: (event: DragEvent) => { + if (canDragFolder) { + onFolderDragStart?.(event, folder.id); + } + }, + onDragEnd: (event: DragEvent) => { + if (canDragFolder) { + onFolderDragEnd?.(event); + } + }, + onRenameChange: setFolderDraft, + onRenameSubmit: () => submitFolderEditing(folder), + onRenameCancel: (event?: React.SyntheticEvent) => cancelFolderEditing(event), + onRenameBegin: (event: React.SyntheticEvent) => { + if (!allowInlineFolderEdit) return; + event.preventDefault(); + event.stopPropagation(); + beginFolderEditing(folder); + }, + }; + + return { + canDragFolder, + isDraggingFolder, + isSelectedFolder, + isFolderEditing, + folderDraftValue, + isFolderSaving, + canSubmitFolder, + allowInlineFolderEdit, + attachFolderInputRef, + handlers, + }; +}; diff --git a/frontend/src/documents/panel/DocumentsPanel.tsx b/frontend/src/documents/panel/DocumentsPanel.tsx index bb476cd..70c75f2 100644 --- a/frontend/src/documents/panel/DocumentsPanel.tsx +++ b/frontend/src/documents/panel/DocumentsPanel.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import DocumentsGrid from '../DocumentsGrid'; -import DocumentsList from '../DocumentsList'; +import { DocumentsList, DocumentsGrid } from '../DocumentsView'; import type { DragEvent, ReactNode, RefObject } from 'react'; import type { DocumentsListEntry, @@ -653,9 +652,9 @@ const DocumentsPanelInner: React.FC = ({ if (!container) return; let selector = null; if (focusedEntryKey.startsWith('document:')) { - selector = `#document-row-${focusedEntryKey.slice('document:'.length)}`; + selector = `#document-${focusedEntryKey.slice('document:'.length)}`; } else if (focusedEntryKey.startsWith('folder:')) { - selector = `#folder-row-${focusedEntryKey.slice('folder:'.length)}`; + selector = `#folder-${focusedEntryKey.slice('folder:'.length)}`; } if (!selector) { return; @@ -690,10 +689,10 @@ const DocumentsPanelInner: React.FC = ({ const activeDescendantId = useMemo(() => { if (!focusedEntryKey) return undefined; if (focusedEntryKey.startsWith('document:')) { - return `document-row-${focusedEntryKey.slice('document:'.length)}`; + return `document-${focusedEntryKey.slice('document:'.length)}`; } if (focusedEntryKey.startsWith('folder:')) { - return `folder-row-${focusedEntryKey.slice('folder:'.length)}`; + return `folder-${focusedEntryKey.slice('folder:'.length)}`; } return undefined; }, [focusedEntryKey]); @@ -785,7 +784,7 @@ const DocumentsPanelInner: React.FC = ({ [entries], ); - const viewProps: DocumentsViewProps = { + const viewProps = { entries, draggingDocumentIdsSet: draggingSet, draggedFolderId, @@ -796,6 +795,7 @@ const DocumentsPanelInner: React.FC = ({ onFolderDrop, onFolderDragStart, onFolderDragEnd, + onFolderRename, onDocumentClick: handleDocumentClick, onDocumentActivate: handleDocumentActivate, onDocumentDragStart: handleDocumentDragStartLocal, @@ -804,22 +804,17 @@ const DocumentsPanelInner: React.FC = ({ onDocumentTagDragLeave: handleDocumentTagDragLeave, onDocumentTagDrop, onDocumentRename, - onFolderRename, ensureAssetUrl, getDocumentAsset, tagLookupById, onTagClick: toggleTagFilter, - onCorrespondentClick: toggleCorrespondentFilter, - activeCorrespondentIdSet: activeCorrespondentIdSet, - activeTagFilters, scrollRef, - // Desk specific - onDocumentStackSelect: handleDeskDocumentStackSelect, - onPromoteSelection: promoteSelectionOrder, + activeCorrespondentIdSet: activeCorrespondentIdSet, + onCorrespondentClick: toggleCorrespondentFilter, tenantId: currentTenantId, viewId: deskViewId, - documentLinks: documentLinkMap, - ensureDownloadUrl, + onDocumentStackSelect: handleDeskDocumentStackSelect, + onPromoteSelection: promoteSelectionOrder, }; const renderBody = () => { diff --git a/frontend/src/hooks/documents/useDocumentDragHandlers.ts b/frontend/src/hooks/documents/useDocumentDragHandlers.ts index 01c413f..422bbe9 100644 --- a/frontend/src/hooks/documents/useDocumentDragHandlers.ts +++ b/frontend/src/hooks/documents/useDocumentDragHandlers.ts @@ -39,7 +39,7 @@ const useDocumentDragHandlers = ({ documentLookup, setDraggedDocumentIds, setDraggedFolderId, - documentsViewMode, + documentsViewMode: _documentsViewMode, }: UseDocumentDragHandlersOptions) => { const dragPreviewRef = useRef(null); const normalizedFolderIds = useMemo( @@ -100,8 +100,7 @@ const useDocumentDragHandlers = ({ if (item.type === 'document') { const doc = item.payload; const rowEl = doc?.id - ? (document.getElementById(`document-row-${doc.id}`) - || document.getElementById(`document-card-${doc.id}`)) + ? document.getElementById(`document-${doc.id}`) : null; const wrapperEl = rowEl instanceof HTMLElement ? rowEl.querySelector('.document-thumbnail-wrapper') @@ -144,8 +143,7 @@ const useDocumentDragHandlers = ({ const payload = item.payload; const folderId = payload as FolderIdentifier; const rowEl = folderId - ? (document.getElementById(`folder-row-${folderId}`) - || document.getElementById(`folder-card-${folderId}`)) + ? document.getElementById(`folder-${folderId}`) : null; const iconEl = rowEl instanceof HTMLElement ? rowEl.querySelector('.thumb-icon, .folder-card__icon') @@ -211,16 +209,13 @@ const useDocumentDragHandlers = ({ return; } - const isGridView = documentsViewMode === 'grid'; const isAlreadySelected = selectedDocumentIds.includes(documentId); const selection: Identifier[] = isAlreadySelected ? [...selectedDocumentIds] - : isGridView - ? [...selectedDocumentIds, documentId] - : [documentId]; + : [documentId]; const folderSelection: FolderIdentifier[] = []; - if (!isAlreadySelected && !isGridView) { + if (!isAlreadySelected) { applySelection([documentKey], { anchor: documentKey, interactedKeys: [documentKey], @@ -271,7 +266,6 @@ const useDocumentDragHandlers = ({ createDragPreview, setDraggedFolderId, setDraggedDocumentIds, - documentsViewMode, ], );