detailpanel

This commit is contained in:
2025-11-01 04:16:31 +01:00
parent fd13edae6d
commit 303ece0529
10 changed files with 1334 additions and 625 deletions
+113 -91
View File
@@ -1,92 +1,99 @@
import React from 'react';
import React, { useMemo } from 'react';
import { DownloadIcon, TextScanIcon, AnalyzeIcon, CloseIcon } from '../ui/icons';
import { describeDocumentSummary } from '../documents/documentSummary';
import DocumentSummarySection, {
buildCorrespondentOptions,
sortCorrespondents,
} from '../documents/DocumentSummarySection';
import { createDocumentActionState } from '../documents/documentActions';
const formatDateTime = (value) => {
if (!value) {
return '—';
}
const date = new Date(value);
return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();
};
const PreviewWorkspace = ({
document,
previewEntry,
tagLookupById,
tagOptions,
onTagAdd,
onTagRemove,
correspondents,
onCorrespondentAdd,
onCorrespondentRemove,
onUpdateTitle,
onUpdateIssued,
resolveFolderPath,
onFolderNavigate,
}) => {
const sortedCorrespondents = useMemo(
() => sortCorrespondents(document?.correspondents || []),
[document],
);
const correspondentOptions = useMemo(
() => buildCorrespondentOptions(Array.isArray(correspondents) ? correspondents : []),
[correspondents],
);
const metadataItems = useMemo(() => {
if (!document) {
return [];
}
return [
{ label: 'Created at', value: formatDateTime(document.created_at) },
{ label: 'Updated at', value: formatDateTime(document.updated_at) },
{
label: 'Filename',
value: document.archive_path || document.filename || '—',
},
{
label: 'Original filename',
value: document.original_name || '—',
},
{
label: 'SHA-256 checksum',
value: document.current_version?.checksum || '—',
},
{
label: 'Content type',
value: document.content_type || '—',
},
];
}, [document]);
const metadataPayload = useMemo(() => {
if (!document || !document.metadata || Object.keys(document.metadata).length === 0) {
return null;
}
return document.metadata;
}, [document]);
if (!document) {
return null;
}
const title = document.title;
const summary = describeDocumentSummary(document);
const correspondents = Array.isArray(document.correspondents)
? document.correspondents.map((entry) => entry?.name).filter(Boolean).join(', ')
: '';
const tags = Array.isArray(document.tags)
? document.tags.map((tag) => tag?.label).filter(Boolean).join(', ')
: '';
const formatDateTime = (value) => {
if (!value) {
return '—';
}
const date = new Date(value);
return Number.isNaN(date.getTime()) ? '—' : date.toLocaleString();
};
const detailItems = [
{ label: 'Title', value: summary.title || '—' },
{ label: 'Archive Reference', value: document.archive_serial || '—' },
{ label: 'Issued On', value: formatDateTime(document.issued_at) },
{ label: 'Correspondent', value: correspondents || '—' },
{
label: 'Filename',
value: document.archive_path || document.filename || '—',
},
{
label: 'Original Filename',
value: document.original_name || '—',
},
{ label: 'Tags', value: tags || '—' },
];
const metadataItems = [
{ label: 'Modified At', value: formatDateTime(document.updated_at) },
{ label: 'Created At', value: formatDateTime(document.created_at) },
{
label: 'Media Filename',
value: document.current_version?.filename || document.archive_path || '—',
},
{
label: 'SHA-256 Checksum',
value: document.current_version?.checksum || '—',
},
{
label: 'Original File Size',
value: summary.sizeLabel,
},
{
label: 'Original MIME Type',
value: document.content_type || '—',
},
];
const metadata =
document.metadata && Object.keys(document.metadata).length > 0 ? document.metadata : null;
return (
<section className="preview-workspace">
<div className="preview-workspace__details">
<section className="preview-section">
<h3 className="preview-section__title">Details</h3>
<dl className="preview-section__list">
{detailItems.map(({ label, value }) => (
<div className="preview-section__item" key={label}>
<dt>{label}</dt>
<dd>{value || '—'}</dd>
</div>
))}
</dl>
</section>
<section className="preview-section">
<h3 className="preview-section__title">Content</h3>
<p className="preview-section__placeholder">
Full OCR text will appear here in a future update. Use the toolbar button to open the OCR view for now.
</p>
</section>
<DocumentSummarySection
document={document}
tagLookupById={tagLookupById}
tagOptions={tagOptions}
onTagAdd={onTagAdd}
onTagRemove={onTagRemove}
correspondents={sortedCorrespondents}
correspondentOptions={correspondentOptions}
onCorrespondentAdd={onCorrespondentAdd}
onCorrespondentRemove={onCorrespondentRemove}
onUpdateTitle={onUpdateTitle}
onUpdateIssued={onUpdateIssued}
resolveFolderPath={resolveFolderPath}
onFolderNavigate={onFolderNavigate}
/>
<section className="preview-section">
<h3 className="preview-section__title">Metadata</h3>
<dl className="preview-section__list">
@@ -97,25 +104,13 @@ const PreviewWorkspace = ({
</div>
))}
</dl>
{metadata ? (
{metadataPayload ? (
<details className="preview-section__payload">
<summary>Show metadata payload</summary>
<pre>{JSON.stringify(metadata, null, 2)}</pre>
<pre>{JSON.stringify(metadataPayload, null, 2)}</pre>
</details>
) : null}
</section>
<section className="preview-section">
<h3 className="preview-section__title">Notes</h3>
<p className="preview-section__placeholder">Custom notes will be editable here once the feature lands.</p>
</section>
<section className="preview-section">
<h3 className="preview-section__title">History</h3>
<p className="preview-section__placeholder">Change history will be displayed here in an upcoming release.</p>
</section>
<section className="preview-section">
<h3 className="preview-section__title">Permissions</h3>
<p className="preview-section__placeholder">Access control management is planned and will surface here.</p>
</section>
</div>
<div className="preview-workspace__viewer">
{!previewEntry?.url ? (
@@ -123,7 +118,7 @@ const PreviewWorkspace = ({
) : (
<iframe
src={previewEntry.url}
title={`Preview of ${title}`}
title={`Preview of ${document.title}`}
className="preview-workspace__object"
/>
)}
@@ -208,6 +203,17 @@ export const createPreviewSurface = ({
onRegenerate,
onClose,
renderSidebarToggle,
tagLookupById,
tagOptions,
onTagAdd,
onTagRemove,
correspondents,
onCorrespondentAdd,
onCorrespondentRemove,
onUpdateTitle,
onUpdateIssued,
resolveFolderPath,
onFolderNavigate,
}) => {
if (!document) {
return null;
@@ -255,7 +261,23 @@ export const createPreviewSurface = ({
key: 'preview',
variant: 'preview',
header,
content: <PreviewWorkspace document={document} previewEntry={previewEntry} />,
content: (
<PreviewWorkspace
document={document}
previewEntry={previewEntry}
tagLookupById={tagLookupById}
tagOptions={tagOptions}
onTagAdd={onTagAdd}
onTagRemove={onTagRemove}
correspondents={correspondents}
onCorrespondentAdd={onCorrespondentAdd}
onCorrespondentRemove={onCorrespondentRemove}
onUpdateTitle={onUpdateTitle}
onUpdateIssued={onUpdateIssued}
resolveFolderPath={resolveFolderPath}
onFolderNavigate={onFolderNavigate}
/>
),
supportsDetail: false,
};
};