diff --git a/src/components/berths/berth-deal-documents-tab.tsx b/src/components/berths/berth-deal-documents-tab.tsx index 2b0c4285..17859612 100644 --- a/src/components/berths/berth-deal-documents-tab.tsx +++ b/src/components/berths/berth-deal-documents-tab.tsx @@ -1,13 +1,15 @@ 'use client'; +import { useState } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; -import { FileText, ExternalLink } from 'lucide-react'; +import { FileText, ExternalLink, Eye } from 'lucide-react'; import { apiFetch } from '@/lib/api/client'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { FilePreviewDialog } from '@/components/files/file-preview-dialog'; interface BerthDealDoc { id: string; @@ -16,6 +18,9 @@ interface BerthDealDoc { status: string; createdAt: string; interestId: string; + fileId: string | null; + fileName: string | null; + mimeType: string | null; } const STATUS_TONE: Record = { @@ -30,6 +35,7 @@ const STATUS_TONE: Record(); const portSlug = params?.portSlug ?? ''; + const [previewDoc, setPreviewDoc] = useState(null); const { data: docs = [], isLoading } = useQuery({ queryKey: ['berth-interest-documents', berthId], @@ -59,32 +65,69 @@ export function BerthDealDocumentsTab({ berthId }: { berthId: string }) {

) : (
    - {docs.map((doc) => ( -
  • -
    - - {doc.title} - {doc.documentType} -
    -
    - {doc.status} - - Open - -
    -
  • - ))} + {docs.map((doc) => { + const canPreview = Boolean(doc.fileId); + return ( +
  • + {/* Title area is itself the preview trigger when a + backing file exists. Falls back to a non-clickable + row for drafts whose PDF hasn't landed yet. */} + {canPreview ? ( + + ) : ( +
    + + {doc.title} + {doc.documentType} + + no file yet + +
    + )} +
    + {doc.status} + + Open + +
    +
  • + ); + })}
)} + + !open && setPreviewDoc(null)} + fileId={previewDoc?.fileId ?? undefined} + fileName={previewDoc?.fileName ?? previewDoc?.title ?? undefined} + mimeType={previewDoc?.mimeType ?? undefined} + /> ); } diff --git a/src/lib/services/documents.service.ts b/src/lib/services/documents.service.ts index 3132daf4..1f041d4c 100644 --- a/src/lib/services/documents.service.ts +++ b/src/lib/services/documents.service.ts @@ -348,6 +348,13 @@ export interface BerthDealDoc { status: string; createdAt: Date; interestId: string; + /** ID of the file blob backing this document (signed PDF if completed, + * otherwise the source upload). Null when no file is attached yet + * (e.g. a draft envelope before its PDF lands). Drives the in-page + * preview affordance — rows without a fileId render as non-clickable. */ + fileId: string | null; + fileName: string | null; + mimeType: string | null; } /** @@ -365,6 +372,14 @@ export async function listDealDocumentsForBerth( portId: string, berthId: string, ): Promise { + // Resolve the preview-worthy file in SQL via COALESCE(signed_file_id, + // file_id) so completed envelopes prefer their signed PDF while drafts + // fall back to the source upload. LEFT JOIN files so envelopes without + // any blob yet (drafts before placement) still appear, just without a + // preview affordance. + const previewFileId = sql< + string | null + >`COALESCE(${documents.signedFileId}, ${documents.fileId})`; const rows = await db .select({ id: documents.id, @@ -373,10 +388,14 @@ export async function listDealDocumentsForBerth( status: documents.status, createdAt: documents.createdAt, interestId: documents.interestId, + fileId: previewFileId, + fileName: files.filename, + mimeType: files.mimeType, }) .from(documents) .innerJoin(interestBerths, eq(interestBerths.interestId, documents.interestId)) .innerJoin(interests, eq(interests.id, documents.interestId)) + .leftJoin(files, and(eq(files.id, previewFileId), eq(files.portId, portId))) .where( and( eq(interestBerths.berthId, berthId), @@ -395,6 +414,9 @@ export async function listDealDocumentsForBerth( status: r.status, createdAt: r.createdAt, interestId: r.interestId, + fileId: r.fileId ?? null, + fileName: r.fileName ?? null, + mimeType: r.mimeType ?? null, })); }