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">
+2
View File
@@ -4856,6 +4856,7 @@ const AppLayout = () => {
onDocumentDragEnd: handleDocumentDragEnd,
isSearchLoading: searchLoading,
tagLookupById,
activeCorrespondentIds: activeCorrespondentFilters,
onDocumentListFocus: handleDocumentListFocus,
onDocumentListKeyDown: handleDocumentListKeyDown,
onFocusedRowChange: setFocusedRowKey,
@@ -4866,6 +4867,7 @@ const AppLayout = () => {
? resolveApiPath(doc.current_version.download_path)
: null,
onTagClick: toggleTagFilter,
onCorrespondentClick: toggleCorrespondentFilter,
onDocumentTagDrop: handleDocumentTagDrop,
viewMode: documentsViewMode,
onViewModeChange: handleDocumentsViewModeChange,
+57
View File
@@ -1631,6 +1631,63 @@ button.danger:hover:not([disabled]) {
gap: 0.25rem;
}
.doc-correspondents {
display: inline;
color: inherit;
}
.doc-correspondent-link {
background: none;
background-color: transparent;
border: none;
padding: 0;
margin: 0;
color: var(--accent, #2563eb);
text-decoration: none;
font: inherit;
cursor: pointer;
box-shadow: none;
appearance: none;
}
.doc-correspondent-link:not(.is-static):hover,
.doc-correspondent-link:not(.is-static):focus-visible {
color: var(--accent-strong, var(--accent));
text-decoration: underline;
text-decoration-thickness: 1.5px;
background-color: transparent;
box-shadow: none;
}
.doc-correspondent-link:not(.is-static):focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
.documents-panel tbody tr.document.selected .doc-correspondents,
.documents-panel tbody tr.document.selected .doc-correspondent-link,
.document-card.selected .doc-correspondent-link {
color: inherit;
}
.document-card.selected .doc-correspondents {
color: inherit;
}
.doc-correspondent-link.is-active {
font-weight: 600;
}
.doc-correspondent-link.is-static {
color: inherit;
cursor: default;
text-decoration: none;
}
.doc-correspondent-link__separator {
color: inherit;
}
.documents-panel tbody tr.document.dragging {
opacity: 0.4;
}