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 { Button } from '@/components/ui/button';
|
||||||
import { AggregatedSection } from './aggregated-section';
|
import { AggregatedSection } from './aggregated-section';
|
||||||
import { SigningDetailsDialog } from './signing-details-dialog';
|
import { SigningDetailsDialog } from './signing-details-dialog';
|
||||||
|
import { FilePreviewDialog } from '@/components/files/file-preview-dialog';
|
||||||
import { useAggregatedFiles, useAggregatedWorkflows } from '@/hooks/use-aggregated-listing';
|
import { useAggregatedFiles, useAggregatedWorkflows } from '@/hooks/use-aggregated-listing';
|
||||||
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
|
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
|
||||||
import type {
|
import type {
|
||||||
@@ -35,6 +36,11 @@ function mapWorkflowStatus(status: string): StatusPillStatus {
|
|||||||
|
|
||||||
export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
|
export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
|
||||||
const [detailsId, setDetailsId] = useState<string | null>(null);
|
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).
|
// Hook data is the bare AggregatedGroup<T>[] array (hooks unwrap the API envelope).
|
||||||
const { data: workflowGroups = [], isLoading: workflowsLoading } = useAggregatedWorkflows(
|
const { data: workflowGroups = [], isLoading: workflowsLoading } = useAggregatedWorkflows(
|
||||||
@@ -76,7 +82,14 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
|
|||||||
const signedFromDocumentId = f.signedFromDocumentId;
|
const signedFromDocumentId = f.signedFromDocumentId;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-2 text-sm">
|
<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">
|
<div className="flex items-center gap-2 text-xs text-muted-foreground tabular-nums">
|
||||||
<span>{new Date(f.createdAt).toLocaleDateString('en-GB')}</span>
|
<span>{new Date(f.createdAt).toLocaleDateString('en-GB')}</span>
|
||||||
{signedFromDocumentId ? (
|
{signedFromDocumentId ? (
|
||||||
@@ -101,6 +114,16 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
|
|||||||
open={Boolean(detailsId)}
|
open={Boolean(detailsId)}
|
||||||
onOpenChange={(open) => !open && setDetailsId(null)}
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { FileText, ClipboardSignature } from 'lucide-react';
|
import { FileText, ClipboardSignature } from 'lucide-react';
|
||||||
|
|
||||||
import { usePaginatedQuery } from '@/hooks/use-paginated-query';
|
import { usePaginatedQuery } from '@/hooks/use-paginated-query';
|
||||||
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
|
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
|
||||||
|
import { FilePreviewDialog } from '@/components/files/file-preview-dialog';
|
||||||
|
|
||||||
interface HubRootDoc {
|
interface HubRootDoc {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -17,6 +19,7 @@ interface HubRootDoc {
|
|||||||
interface HubRootFile {
|
interface HubRootFile {
|
||||||
id: string;
|
id: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
|
mimeType: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +39,12 @@ const STATUS_PILL_MAP: Record<string, StatusPillStatus> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function HubRootView({ portSlug }: Props) {
|
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>({
|
const { data: workflows, isLoading: workflowsLoading } = usePaginatedQuery<HubRootDoc>({
|
||||||
queryKey: ['documents', 'hub-root', 'workflows'],
|
queryKey: ['documents', 'hub-root', 'workflows'],
|
||||||
endpoint: '/api/v1/documents?tab=in_progress',
|
endpoint: '/api/v1/documents?tab=in_progress',
|
||||||
@@ -87,7 +96,16 @@ export function HubRootView({ portSlug }: Props) {
|
|||||||
<ul className="divide-y">
|
<ul className="divide-y">
|
||||||
{filesData.map((f) => (
|
{filesData.map((f) => (
|
||||||
<li key={f.id} className="flex items-center justify-between px-3 py-2 text-sm">
|
<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">
|
<span className="text-xs text-muted-foreground tabular-nums">
|
||||||
{new Date(f.createdAt).toLocaleDateString('en-GB')}
|
{new Date(f.createdAt).toLocaleDateString('en-GB')}
|
||||||
</span>
|
</span>
|
||||||
@@ -96,6 +114,16 @@ export function HubRootView({ portSlug }: Props) {
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<FilePreviewDialog
|
||||||
|
open={Boolean(previewFile)}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) setPreviewFile(null);
|
||||||
|
}}
|
||||||
|
fileId={previewFile?.id}
|
||||||
|
fileName={previewFile?.name}
|
||||||
|
mimeType={previewFile?.mimeType ?? undefined}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user