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>
);
}

View File

@@ -1,10 +1,12 @@
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { FileText, ClipboardSignature } from 'lucide-react';
import { usePaginatedQuery } from '@/hooks/use-paginated-query';
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import { FilePreviewDialog } from '@/components/files/file-preview-dialog';
interface HubRootDoc {
id: string;
@@ -17,6 +19,7 @@ interface HubRootDoc {
interface HubRootFile {
id: string;
filename: string;
mimeType: string | null;
createdAt: string;
}
@@ -36,6 +39,12 @@ const STATUS_PILL_MAP: Record<string, StatusPillStatus> = {
};
export function HubRootView({ portSlug }: Props) {
const [previewFile, setPreviewFile] = useState<{
id: string;
name: string;
mimeType: string | null;
} | null>(null);
const { data: workflows, isLoading: workflowsLoading } = usePaginatedQuery<HubRootDoc>({
queryKey: ['documents', 'hub-root', 'workflows'],
endpoint: '/api/v1/documents?tab=in_progress',
@@ -87,7 +96,16 @@ export function HubRootView({ portSlug }: Props) {
<ul className="divide-y">
{filesData.map((f) => (
<li key={f.id} className="flex items-center justify-between px-3 py-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>
<span className="text-xs text-muted-foreground tabular-nums">
{new Date(f.createdAt).toLocaleDateString('en-GB')}
</span>
@@ -96,6 +114,16 @@ export function HubRootView({ portSlug }: Props) {
</ul>
)}
</section>
<FilePreviewDialog
open={Boolean(previewFile)}
onOpenChange={(open) => {
if (!open) setPreviewFile(null);
}}
fileId={previewFile?.id}
fileName={previewFile?.name}
mimeType={previewFile?.mimeType ?? undefined}
/>
</div>
);
}