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 ? (
+ setPreviewDoc(doc)}
+ className="flex min-w-0 flex-1 items-center gap-2 rounded -mx-1 px-1 py-0.5 text-left hover:bg-muted/60 focus-visible:bg-muted/60 focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring"
+ title="Preview document"
+ >
+
+ {doc.title}
+ {doc.documentType}
+
+
+ ) : (
+
+
+ {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,
}));
}