import React from 'react'; import { FolderIcon, CheckIcon, CloseIcon } from '../ui/icons'; import { getTagColorStyle } from '../utils/colors'; import DocumentThumbnailImage from './DocumentThumbnailImage'; import CorrespondentLinks from './CorrespondentLinks'; import { resolveCorrespondents } from './correspondents'; import { writeTagTransferData } from './tagTransfer'; import useInlineRename from './useInlineRename'; const formatDate = (value) => { if (!value) { return "—"; } const timestamp = Date.parse(value); if (Number.isNaN(timestamp)) { return "—"; } return new Date(timestamp).toLocaleDateString(); }; const DocumentsList = ({ entries, focusedRowKey, selectedDocumentIdsSet, selectedFolderIdsSet, draggingDocumentIdsSet, draggedFolderId, ensureAssetUrl, getDocumentAsset, onFolderClick, onFolderSelect, onFolderDragOver, onFolderDragLeave, onFolderDrop, onFolderDragStart, onFolderDragEnd, onFolderRename, onDocumentClick, onDocumentOpen, onDocumentDragStart, onDocumentDragEnd, onDocumentTagDragOver, onDocumentTagDragLeave, onDocumentTagDrop, onDocumentRename, tagLookupById, onTagClick, onCorrespondentClick, activeCorrespondentIdSet, scrollRef, onClearSelection, }) => { 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 ( { 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 rowKey = `folder:${folder.id}`; 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)} 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={() => onDocumentOpen?.(doc.id)} draggable onDragStart={(event) => onDocumentDragStart?.(event, doc)} onDragEnd={(event) => onDocumentDragEnd?.(event)} onDragOver={onDocumentTagDragOver} onDragLeave={onDocumentTagDragLeave} onDrop={(event) => onDocumentTagDrop?.(event, doc.id)} > ); })}
Name Issued Added
{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) => { 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} ); })}
)}
{issuedLabel} {addedLabel}
); }; export default DocumentsList;