feat(uat-batch-24): click-to-preview on EntityFolderView + HubRootView Files

Completes the click-to-preview sweep across all file-row surfaces. The
filename cells in entity-folder-view.tsx (entity-scoped Files panel)
and hub-root-view.tsx (Documents Hub root "Recent files") were the
last two non-clickable surfaces — both now wrap the filename in a
button that opens FilePreviewDialog directly, matching the FileGrid
and DocumentList pattern shipped in 52342ee.

HubRootFile shape extended to include mimeType (already returned by
the /api/v1/files endpoint via the buildListQuery passthrough) so the
preview dialog can branch on image vs PDF without a second request.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 19:21:11 +02:00
parent a263a202d9
commit ded16f4a5b
2 changed files with 53 additions and 2 deletions

View File

@@ -7,6 +7,7 @@ import { ClipboardSignature, FileText, Eye } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { AggregatedSection } from './aggregated-section';
import { SigningDetailsDialog } from './signing-details-dialog';
import { FilePreviewDialog } from '@/components/files/file-preview-dialog';
import { useAggregatedFiles, useAggregatedWorkflows } from '@/hooks/use-aggregated-listing';
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import type {
@@ -35,6 +36,11 @@ function mapWorkflowStatus(status: string): StatusPillStatus {
export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
const [detailsId, setDetailsId] = useState<string | null>(null);
const [previewFile, setPreviewFile] = useState<{
id: string;
name: string;
mimeType: string | null;
} | null>(null);
// Hook data is the bare AggregatedGroup<T>[] array (hooks unwrap the API envelope).
const { data: workflowGroups = [], isLoading: workflowsLoading } = useAggregatedWorkflows(
@@ -76,7 +82,14 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
const signedFromDocumentId = f.signedFromDocumentId;
return (
<div className="flex items-center justify-between gap-2 text-sm">
<span className="truncate">{f.filename}</span>
<button
type="button"
className="truncate text-left hover:text-brand hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 rounded-sm"
onClick={() => setPreviewFile({ id: f.id, name: f.filename, mimeType: f.mimeType })}
aria-label={`Preview ${f.filename}`}
>
{f.filename}
</button>
<div className="flex items-center gap-2 text-xs text-muted-foreground tabular-nums">
<span>{new Date(f.createdAt).toLocaleDateString('en-GB')}</span>
{signedFromDocumentId ? (
@@ -101,6 +114,16 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
open={Boolean(detailsId)}
onOpenChange={(open) => !open && setDetailsId(null)}
/>
<FilePreviewDialog
open={Boolean(previewFile)}
onOpenChange={(open) => {
if (!open) setPreviewFile(null);
}}
fileId={previewFile?.id}
fileName={previewFile?.name}
mimeType={previewFile?.mimeType ?? undefined}
/>
</div>
);
}