diff --git a/frontend/src/documents/DocumentsTable.jsx b/frontend/src/documents/DocumentsTable.jsx
index 780e22a..74b95de 100644
--- a/frontend/src/documents/DocumentsTable.jsx
+++ b/frontend/src/documents/DocumentsTable.jsx
@@ -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 (
+
+
+ {!isLast ? (
+ ,
+ ) : null}
+
+ );
+ }),
+ [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 ? (
+
+ {renderCorrespondentLinks(correspondents)}
+
+ ) : null}
+ {doc.title || doc.original_name}
{visibleTags.length > 0 && (
@@ -606,8 +697,7 @@ const DocumentsTable = ({
| Preview |
Name |
- Type |
- Updated |
+ Issued |
Actions |
@@ -671,7 +761,6 @@ const DocumentsTable = ({
{folder.name}
- Folder |
— |
@@ -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 (
- {doc.title || doc.original_name}
+
+ {correspondents.length > 0 ? (
+
+ {renderCorrespondentLinks(correspondents)}
+
+ ) : null}
+ {doc.title || doc.original_name}
+
{(doc.tags || []).length > 0 && (
@@ -810,11 +907,18 @@ const DocumentsTable = ({
)}
- {doc.content_type || 'Document'} |
- {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();
+ })()}
|
diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx
index 2de5e1b..64bb048 100644
--- a/frontend/src/index.jsx
+++ b/frontend/src/index.jsx
@@ -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,
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index b205cab..72c41f5 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -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;
}
| |