From 85f3d90329f6ee89426999cf92853db022180451 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Thu, 30 Oct 2025 18:12:01 +0100 Subject: [PATCH] frontend cleanup --- frontend/src/detail/DetailPanel.jsx | 240 +++++++++++----------- frontend/src/documents/documentActions.js | 75 +++++++ frontend/src/documents/documentSummary.js | 140 +++++++++++++ frontend/src/index.jsx | 23 ++- frontend/src/preview/PreviewWorkspace.jsx | 113 +++++----- frontend/src/sidebar/Sidebar.jsx | 158 +++++++------- frontend/src/styles.css | 53 +++-- 7 files changed, 510 insertions(+), 292 deletions(-) create mode 100644 frontend/src/documents/documentActions.js create mode 100644 frontend/src/documents/documentSummary.js diff --git a/frontend/src/detail/DetailPanel.jsx b/frontend/src/detail/DetailPanel.jsx index ac86ccf..4b74640 100644 --- a/frontend/src/detail/DetailPanel.jsx +++ b/frontend/src/detail/DetailPanel.jsx @@ -13,8 +13,9 @@ import { getTagColorStyle } from '../utils/colors'; import { formatFileSize } from '../utils/format'; import { resolveDocumentAssetUrl, createAssetView } from '../asset_manager'; import { useAssetNavigator } from '../hooks/useAssetNavigator'; +import { describeDocumentSummary } from '../documents/documentSummary'; +import { createDocumentActionState } from '../documents/documentActions'; import PreviewZoomOverlay from './PreviewZoomOverlay'; -import { openOcrTextInNewTab } from '../utils/ocr'; const MAX_PREVIEW_STACK_ITEMS = 15; @@ -314,14 +315,29 @@ const DetailPanel = ({ [selectedDocuments], ); - const singleDownloadHref = useMemo(() => { - if (!singleDoc) return null; - const downloadPath = singleDoc.current_version?.download_path; - if (!downloadPath || !resolveApiPath) { - return null; + const { downloadHref: singleDownloadHref, hasOcr: singleHasOcr, openOcr } = useMemo( + () => + createDocumentActionState({ + document: singleDoc, + resolveApiPath, + ensurePreviewData, + ensureAssetUrl, + getDocumentAsset, + }), + [singleDoc, resolveApiPath, ensurePreviewData, ensureAssetUrl, getDocumentAsset], + ); + + const detailSummary = useMemo(() => describeDocumentSummary(singleDoc), [singleDoc]); + + const headerTitle = useMemo(() => { + if (selectedCount === 0) { + return 'Document details'; } - return resolveApiPath(downloadPath); - }, [singleDoc, resolveApiPath]); + if (selectedCount === 1) { + return detailSummary.title; + } + return `${selectedCount} document${selectedCount === 1 ? '' : 's'}`; + }, [selectedCount, detailSummary]); const [titleEditDocId, setTitleEditDocId] = useState(null); const [titleDraft, setTitleDraft] = useState(''); @@ -403,27 +419,6 @@ const DetailPanel = ({ [onPromoteSelection], ); - const hasOcrAsset = useMemo( - () => Boolean(singleDoc && getDocumentAsset(singleDoc, 'ocr-text')), - [singleDoc, getDocumentAsset], - ); - - const openOcr = useCallback(async () => { - if (!singleDoc) { - return; - } - try { - await openOcrTextInNewTab({ - document: singleDoc, - ensurePreviewData, - getDocumentAsset, - ensureAssetUrl, - }); - } catch (error) { - /* noop */ - } - }, [singleDoc, ensurePreviewData, getDocumentAsset, ensureAssetUrl]); - const singlePreviewNavigator = useAssetNavigator({ document: singleDoc, assetType: 'preview', @@ -614,6 +609,90 @@ const DetailPanel = ({ return segments; }, [singleDoc?.folder_id, resolveFolderPath]); + const folderLabel = detailSummary.folderLabel; + + const folderDisplayNode = useMemo(() => { + if (!singleDoc) { + return folderLabel || '—'; + } + if (!singleFolderPath?.length) { + return folderLabel || '—'; + } + return ( + + {singleFolderPath.map((segment, index) => { + const label = segment?.name || '…'; + const targetId = segment?.id || null; + const key = `${targetId || label}-${index}`; + const isClickable = Boolean(targetId) && typeof onFolderNavigate === 'function'; + const href = !isClickable + ? null + : targetId === 'root' + ? '/documents' + : `/documents/folder/${targetId}`; + return ( + + {index > 0 ? / : null} + {isClickable ? ( + { + if ( + event.button !== 0 || + event.metaKey || + event.ctrlKey || + event.shiftKey || + event.altKey + ) { + return; + } + event.preventDefault(); + event.stopPropagation(); + onFolderNavigate(targetId); + }} + > + {label} + + ) : ( + {label} + )} + + ); + })} + + ); + }, [singleDoc, singleFolderPath, folderLabel, onFolderNavigate]); + + const detailInfoRows = useMemo(() => { + if (!singleDoc) { + return []; + } + const allowedKeys = new Set(['uploaded', 'size', 'type', 'issued', 'pages', 'created', 'updated', 'folder']); + const rows = detailSummary.summaryRows + .filter((row) => { + if (!allowedKeys.has(row.key)) { + return false; + } + if (row.key === 'pages') { + return Number.isFinite(detailSummary.pageCount); + } + if (row.key === 'folder') { + return Boolean(singleFolderPath?.length); + } + return true; + }) + .map((row) => (row.key === 'folder' ? { ...row, value: folderDisplayNode } : row)); + + rows.push({ + key: 'original-name', + label: 'Original filename', + value: singleDoc.original_name || '—', + }); + + return rows; + }, [singleDoc, detailSummary, folderDisplayNode, singleFolderPath]); + const bulkCorrespondents = useMemo(() => { if (selectedDocuments.length <= 1) { const doc = selectedDocuments[0]; @@ -890,20 +969,7 @@ const DetailPanel = ({ const displayName = singleDoc.title || singleDoc.original_name; const isEditingTitle = titleEditDocId === singleDoc.id; - const sizeBytes = Number(singleDoc.current_version?.size_bytes) || 0; - const sizeLabel = sizeBytes > 0 ? formatFileSize(sizeBytes) : '—'; - const issuedAt = singleDoc.issued_at - ? new Date(singleDoc.issued_at).toLocaleString() - : '—'; const tagsForDoc = Array.isArray(singleDoc.tags) ? singleDoc.tags : []; - const pageCountRaw = singleDoc.current_version?.metadata?.page_count; - const pageCountValue = - typeof pageCountRaw === 'number' - ? pageCountRaw - : pageCountRaw != null && pageCountRaw !== '' - ? Number.parseInt(pageCountRaw, 10) - : null; - const hasPageCount = Number.isFinite(pageCountValue) && pageCountValue >= 0; const metadata = singleDoc.metadata && Object.keys(singleDoc.metadata).length > 0 ? singleDoc.metadata : null; const effectiveCardinality = singleEffectiveCardinality; @@ -1014,76 +1080,17 @@ const DetailPanel = ({ {titleError ?
{titleError}
: null}
-
- Uploaded:{' '} - {singleDoc.uploaded_at ? new Date(singleDoc.uploaded_at).toLocaleString() : '—'} -
-
- Size:{' '} - {sizeLabel} -
-
- Type: {singleDoc.content_type || 'Unknown'} -
-
- Issued: {issuedAt} -
- {hasPageCount ? ( -
- Pages: {pageCountValue} -
- ) : null} - {singleFolderPath?.length ? ( -
- Folder:{' '} - - {singleFolderPath.map((segment, index) => { - const label = segment?.name || '…'; - const targetId = segment?.id || null; - const key = `${targetId || label}-${index}`; - const isClickable = Boolean(targetId) && typeof onFolderNavigate === 'function'; - const href = !isClickable - ? null - : targetId === 'root' - ? '/documents' - : `/documents/folder/${targetId}`; - return ( - - {index > 0 ? / : null} - {isClickable ? ( - { - if ( - event.button !== 0 || - event.metaKey || - event.ctrlKey || - event.shiftKey || - event.altKey - ) { - return; - } - event.preventDefault(); - event.stopPropagation(); - onFolderNavigate(targetId); - }} - > - {label} - - ) : ( - {label} - )} - - ); - })} - -
- ) : null} -
- Original filename:{' '} - {singleDoc.original_name} -
+ {detailInfoRows.map((row) => { + const rawValue = row.value; + const displayValue = + rawValue === null || rawValue === undefined || rawValue === '' ? '—' : rawValue; + return ( +
+ {row.label}:{' '} + {displayValue} +
+ ); + })}
1; - const showOcrAction = Boolean(singleDoc && hasOcrAsset); + const showOcrAction = Boolean(singleDoc && singleHasOcr); return ( <>