feat(berth-deal-docs): clickable rows open in-page file preview

The Interest Documents tab on the berth detail page listed deal docs
read-only with only an "Open" link to the interest detail page —
forced reps to navigate away just to see the PDF. Now every row whose
backing PDF exists opens the existing FilePreviewDialog inline.

- Service: listDealDocumentsForBerth now joins files and returns
  fileId (COALESCE(signedFileId, fileId) so completed envelopes
  prefer the signed PDF), fileName, mimeType. Drafts without a blob
  yet still appear, just non-clickable.
- UI: row title area is a button that triggers FilePreviewDialog;
  Eye affordance on hover. Falls back to a "no file yet" hint when
  the document has no backing blob. "Open" link stays as the
  secondary "go to interest" action.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 18:20:01 +02:00
parent 400ff993d2
commit c8869338e8
2 changed files with 88 additions and 23 deletions

View File

@@ -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<BerthDealDoc[]> {
// 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,
}));
}