This commit is contained in:
2025-10-27 20:53:51 +01:00
parent 7366d2e16b
commit a0be094cbd
3 changed files with 172 additions and 9 deletions
+113 -9
View File
@@ -13,6 +13,40 @@ const getPageCount = (doc) =>
? doc.current_version.metadata.page_count
: null;
const resolveCorrespondents = (doc) => {
if (!doc || !Array.isArray(doc.correspondents)) {
return [];
}
const seen = new Set();
const results = [];
doc.correspondents.forEach((entry, index) => {
if (!entry) return;
const id = entry.id ?? entry.correspondent_id ?? null;
const name = (entry.name || entry.label || entry.slug || '').trim();
if (!name) return;
if (id && seen.has(id)) {
return;
}
if (id) {
seen.add(id);
}
results.push({
id,
name,
role: entry.role || null,
key: id ?? `${name}-${index}`,
});
});
return results;
};
// Detects when an element becomes visible within a scroll container.
const useLazyVisibility = (rootRef, resetKey) => {
const targetRef = useRef(null);
@@ -165,6 +199,7 @@ const DocumentsTable = ({
onFolderRename,
onDocumentRename,
tagLookupById,
activeCorrespondentIds = [],
onDocumentListFocus,
onDocumentListKeyDown,
onFocusedRowChange,
@@ -172,6 +207,7 @@ const DocumentsTable = ({
getDocumentAsset = () => null,
getDownloadHref,
onTagClick,
onCorrespondentClick,
isSearchLoading = false,
onDocumentTagDrop,
viewMode = 'list',
@@ -194,6 +230,10 @@ const DocumentsTable = ({
() => new Set(draggingDocumentIds || []),
[draggingDocumentIds],
);
const activeCorrespondentIdSet = useMemo(
() => new Set(activeCorrespondentIds || []),
[activeCorrespondentIds],
);
const scrollRef = useRef(null);
const [, forceVisibilityTick] = useState(0);
const lastScrollNodeRef = useRef(null);
@@ -333,6 +373,51 @@ const DocumentsTable = ({
[isTagDragEvent, onDocumentTagDrop],
);
const renderCorrespondentLinks = useCallback(
(correspondents) =>
correspondents.map((correspondent, index) => {
const isActive = correspondent.id != null && activeCorrespondentIdSet.has(correspondent.id);
const hasClickHandler = Boolean(onCorrespondentClick) && correspondent.id != null;
const classNames = ['doc-correspondent-link'];
if (isActive) classNames.push('is-active');
if (!hasClickHandler) classNames.push('is-static');
const isLast = index === correspondents.length - 1;
const label = isLast
? `${correspondent.name}:${String.fromCharCode(160)}`
: correspondent.name;
return (
<React.Fragment key={correspondent.key ?? correspondent.id ?? `${correspondent.name}-${index}`}>
<button
type="button"
className={classNames.join(' ')}
aria-disabled={hasClickHandler ? undefined : true}
onClick={(event) => {
if (!hasClickHandler) {
return;
}
event.stopPropagation();
onCorrespondentClick(correspondent.id, correspondent);
}}
onKeyDown={(event) => {
if (!hasClickHandler) {
return;
}
if (event.key === ' ' || event.key === 'Enter') {
event.stopPropagation();
}
}}
>
{label}
</button>
{!isLast ? (
<span className="doc-correspondent-link__separator">, </span>
) : null}
</React.Fragment>
);
}),
[activeCorrespondentIdSet, onCorrespondentClick],
);
const showDefaultEmptyState = !showingSearchResults && !subfolders.length && rows.length === 0;
const showListSearchEmptyState =
showingSearchResults && rows.length === 0 && !isGridView && !isSearchLoading;
@@ -498,6 +583,7 @@ const DocumentsTable = ({
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');
@@ -533,7 +619,12 @@ const DocumentsTable = ({
className="document-card__title"
title={doc.title || doc.original_name}
>
{doc.title || doc.original_name}
{correspondents.length > 0 ? (
<span className="doc-correspondents">
{renderCorrespondentLinks(correspondents)}
</span>
) : null}
<span>{doc.title || doc.original_name}</span>
</div>
{visibleTags.length > 0 && (
<div className="document-card__tags">
@@ -606,8 +697,7 @@ const DocumentsTable = ({
<tr>
<th className="thumb-column">Preview</th>
<th>Name</th>
<th>Type</th>
<th>Updated</th>
<th>Issued</th>
<th className="actions-column">Actions</th>
</tr>
</thead>
@@ -671,7 +761,6 @@ const DocumentsTable = ({
<span>{folder.name}</span>
</div>
</td>
<td>Folder</td>
<td></td>
<td className="actions">
<div className="action-buttons">
@@ -721,6 +810,7 @@ const DocumentsTable = ({
if (isSelected) rowClasses.push('selected');
if (isDraggingDoc) rowClasses.push('is-dragging');
const downloadHref = getDownloadHref?.(doc) || null;
const correspondents = resolveCorrespondents(doc);
return (
<tr
@@ -749,7 +839,14 @@ const DocumentsTable = ({
<td className="doc-list__name">
<div className="doc-name">
<div className="doc-list__name-content">
<span className="doc-name__title">{doc.title || doc.original_name}</span>
<span className="doc-name__title">
{correspondents.length > 0 ? (
<span className="doc-correspondents">
{renderCorrespondentLinks(correspondents)}
</span>
) : null}
<span>{doc.title || doc.original_name}</span>
</span>
</div>
{(doc.tags || []).length > 0 && (
<div className="doc-name__tags">
@@ -810,11 +907,18 @@ const DocumentsTable = ({
)}
</div>
</td>
<td>{doc.content_type || 'Document'}</td>
<td>
{doc.updated_at
? new Date(doc.updated_at).toLocaleString()
: '—'}
{(() => {
const issuedAt = doc.issued_at || doc.updated_at || null;
if (!issuedAt) {
return '—';
}
const timestamp = Date.parse(issuedAt);
if (Number.isNaN(timestamp)) {
return '—';
}
return new Date(timestamp).toLocaleDateString();
})()}
</td>
<td className="actions">
<div className="action-buttons">