import React 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 } from './tagTransfer'; import useInlineRename from './useInlineRename'; const DocumentsGrid = ({ entries, selectedDocumentIdsSet, selectedFolderIdsSet, draggingDocumentIdsSet, draggedFolderId, onFolderClick, onFolderSelect, onFolderDragOver, onFolderDragLeave, onFolderDrop, onFolderDragStart, onFolderDragEnd, onDocumentClick, onDocumentActivate, onDocumentDragStart, onDocumentDragEnd, onDocumentTagDragOver, onDocumentTagDragLeave, onDocumentTagDrop, ensureAssetUrl, getDocumentAsset, gridIconSize, tagLookupById, onTagClick, scrollRef, onCorrespondentClick, activeCorrespondentIdSet, onClearSelection, onDocumentRename, onFolderRename, }) => { const { editingId: editingDocumentId, draftValue: documentDraft, setDraftValue: setDocumentDraft, beginEditing: beginDocumentEditing, cancelEditing: cancelDocumentEditing, submitEditing: submitDocumentEditing, savingId: savingDocumentId, attachInputRef: attachDocumentInputRef, } = useInlineRename(onDocumentRename, { getCurrentValue: (doc) => doc?.title ?? '', getEntityId: (doc) => 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?.name ?? '', getEntityId: (folder) => folder?.id ?? null, }); const documentSelectionCount = selectedDocumentIdsSet?.size ?? 0; const folderSelectionCount = selectedFolderIdsSet?.size ?? 0; const totalSelectionCount = documentSelectionCount + folderSelectionCount; return (
{ if (event.target === event.currentTarget) { onClearSelection?.(); } }} > {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 = folder.id !== 'root' && typeof onFolderRename === 'function'; 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) => onDocumentTagDrop?.(event, doc.id)} onDropCapture={(event) => onDocumentTagDrop?.(event, doc.id)} >
{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) => { const colorSource = tag?.color || tagLookupById?.get(tag.id)?.color; const style = getTagColorStyle(colorSource); return ( { event.stopPropagation(); onTagClick?.(tag.id); }} 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={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); event.stopPropagation(); onTagClick?.(tag.id); } }} > {tag.label} ); })} {remainingTagCount > 0 && ( +{remainingTagCount} )}
)}
); })}
); }; export default DocumentsGrid;