table
This commit is contained in:
@@ -13,6 +13,40 @@ const getPageCount = (doc) =>
|
|||||||
? doc.current_version.metadata.page_count
|
? doc.current_version.metadata.page_count
|
||||||
: null;
|
: 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.
|
// Detects when an element becomes visible within a scroll container.
|
||||||
const useLazyVisibility = (rootRef, resetKey) => {
|
const useLazyVisibility = (rootRef, resetKey) => {
|
||||||
const targetRef = useRef(null);
|
const targetRef = useRef(null);
|
||||||
@@ -165,6 +199,7 @@ const DocumentsTable = ({
|
|||||||
onFolderRename,
|
onFolderRename,
|
||||||
onDocumentRename,
|
onDocumentRename,
|
||||||
tagLookupById,
|
tagLookupById,
|
||||||
|
activeCorrespondentIds = [],
|
||||||
onDocumentListFocus,
|
onDocumentListFocus,
|
||||||
onDocumentListKeyDown,
|
onDocumentListKeyDown,
|
||||||
onFocusedRowChange,
|
onFocusedRowChange,
|
||||||
@@ -172,6 +207,7 @@ const DocumentsTable = ({
|
|||||||
getDocumentAsset = () => null,
|
getDocumentAsset = () => null,
|
||||||
getDownloadHref,
|
getDownloadHref,
|
||||||
onTagClick,
|
onTagClick,
|
||||||
|
onCorrespondentClick,
|
||||||
isSearchLoading = false,
|
isSearchLoading = false,
|
||||||
onDocumentTagDrop,
|
onDocumentTagDrop,
|
||||||
viewMode = 'list',
|
viewMode = 'list',
|
||||||
@@ -194,6 +230,10 @@ const DocumentsTable = ({
|
|||||||
() => new Set(draggingDocumentIds || []),
|
() => new Set(draggingDocumentIds || []),
|
||||||
[draggingDocumentIds],
|
[draggingDocumentIds],
|
||||||
);
|
);
|
||||||
|
const activeCorrespondentIdSet = useMemo(
|
||||||
|
() => new Set(activeCorrespondentIds || []),
|
||||||
|
[activeCorrespondentIds],
|
||||||
|
);
|
||||||
const scrollRef = useRef(null);
|
const scrollRef = useRef(null);
|
||||||
const [, forceVisibilityTick] = useState(0);
|
const [, forceVisibilityTick] = useState(0);
|
||||||
const lastScrollNodeRef = useRef(null);
|
const lastScrollNodeRef = useRef(null);
|
||||||
@@ -333,6 +373,51 @@ const DocumentsTable = ({
|
|||||||
[isTagDragEvent, onDocumentTagDrop],
|
[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 showDefaultEmptyState = !showingSearchResults && !subfolders.length && rows.length === 0;
|
||||||
const showListSearchEmptyState =
|
const showListSearchEmptyState =
|
||||||
showingSearchResults && rows.length === 0 && !isGridView && !isSearchLoading;
|
showingSearchResults && rows.length === 0 && !isGridView && !isSearchLoading;
|
||||||
@@ -498,6 +583,7 @@ const DocumentsTable = ({
|
|||||||
const tagList = Array.isArray(doc.tags) ? doc.tags : [];
|
const tagList = Array.isArray(doc.tags) ? doc.tags : [];
|
||||||
const visibleTags = tagList.slice(0, 3);
|
const visibleTags = tagList.slice(0, 3);
|
||||||
const remainingTagCount = tagList.length > 3 ? tagList.length - 3 : 0;
|
const remainingTagCount = tagList.length > 3 ? tagList.length - 3 : 0;
|
||||||
|
const correspondents = resolveCorrespondents(doc);
|
||||||
const cardClasses = ['document-card', 'document'];
|
const cardClasses = ['document-card', 'document'];
|
||||||
if (isSelected) cardClasses.push('selected');
|
if (isSelected) cardClasses.push('selected');
|
||||||
if (isDraggingDoc) cardClasses.push('is-dragging');
|
if (isDraggingDoc) cardClasses.push('is-dragging');
|
||||||
@@ -533,7 +619,12 @@ const DocumentsTable = ({
|
|||||||
className="document-card__title"
|
className="document-card__title"
|
||||||
title={doc.title || doc.original_name}
|
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>
|
</div>
|
||||||
{visibleTags.length > 0 && (
|
{visibleTags.length > 0 && (
|
||||||
<div className="document-card__tags">
|
<div className="document-card__tags">
|
||||||
@@ -606,8 +697,7 @@ const DocumentsTable = ({
|
|||||||
<tr>
|
<tr>
|
||||||
<th className="thumb-column">Preview</th>
|
<th className="thumb-column">Preview</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Type</th>
|
<th>Issued</th>
|
||||||
<th>Updated</th>
|
|
||||||
<th className="actions-column">Actions</th>
|
<th className="actions-column">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -671,7 +761,6 @@ const DocumentsTable = ({
|
|||||||
<span>{folder.name}</span>
|
<span>{folder.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>Folder</td>
|
|
||||||
<td>—</td>
|
<td>—</td>
|
||||||
<td className="actions">
|
<td className="actions">
|
||||||
<div className="action-buttons">
|
<div className="action-buttons">
|
||||||
@@ -721,6 +810,7 @@ const DocumentsTable = ({
|
|||||||
if (isSelected) rowClasses.push('selected');
|
if (isSelected) rowClasses.push('selected');
|
||||||
if (isDraggingDoc) rowClasses.push('is-dragging');
|
if (isDraggingDoc) rowClasses.push('is-dragging');
|
||||||
const downloadHref = getDownloadHref?.(doc) || null;
|
const downloadHref = getDownloadHref?.(doc) || null;
|
||||||
|
const correspondents = resolveCorrespondents(doc);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
@@ -749,7 +839,14 @@ const DocumentsTable = ({
|
|||||||
<td className="doc-list__name">
|
<td className="doc-list__name">
|
||||||
<div className="doc-name">
|
<div className="doc-name">
|
||||||
<div className="doc-list__name-content">
|
<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>
|
</div>
|
||||||
{(doc.tags || []).length > 0 && (
|
{(doc.tags || []).length > 0 && (
|
||||||
<div className="doc-name__tags">
|
<div className="doc-name__tags">
|
||||||
@@ -810,11 +907,18 @@ const DocumentsTable = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{doc.content_type || 'Document'}</td>
|
|
||||||
<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>
|
||||||
<td className="actions">
|
<td className="actions">
|
||||||
<div className="action-buttons">
|
<div className="action-buttons">
|
||||||
|
|||||||
@@ -4856,6 +4856,7 @@ const AppLayout = () => {
|
|||||||
onDocumentDragEnd: handleDocumentDragEnd,
|
onDocumentDragEnd: handleDocumentDragEnd,
|
||||||
isSearchLoading: searchLoading,
|
isSearchLoading: searchLoading,
|
||||||
tagLookupById,
|
tagLookupById,
|
||||||
|
activeCorrespondentIds: activeCorrespondentFilters,
|
||||||
onDocumentListFocus: handleDocumentListFocus,
|
onDocumentListFocus: handleDocumentListFocus,
|
||||||
onDocumentListKeyDown: handleDocumentListKeyDown,
|
onDocumentListKeyDown: handleDocumentListKeyDown,
|
||||||
onFocusedRowChange: setFocusedRowKey,
|
onFocusedRowChange: setFocusedRowKey,
|
||||||
@@ -4866,6 +4867,7 @@ const AppLayout = () => {
|
|||||||
? resolveApiPath(doc.current_version.download_path)
|
? resolveApiPath(doc.current_version.download_path)
|
||||||
: null,
|
: null,
|
||||||
onTagClick: toggleTagFilter,
|
onTagClick: toggleTagFilter,
|
||||||
|
onCorrespondentClick: toggleCorrespondentFilter,
|
||||||
onDocumentTagDrop: handleDocumentTagDrop,
|
onDocumentTagDrop: handleDocumentTagDrop,
|
||||||
viewMode: documentsViewMode,
|
viewMode: documentsViewMode,
|
||||||
onViewModeChange: handleDocumentsViewModeChange,
|
onViewModeChange: handleDocumentsViewModeChange,
|
||||||
|
|||||||
@@ -1631,6 +1631,63 @@ button.danger:hover:not([disabled]) {
|
|||||||
gap: 0.25rem;
|
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 {
|
.documents-panel tbody tr.document.dragging {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user