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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user