feat(po-detail): show all attachments grouped by type

Split the flat document list into two labeled sections: Submission
(po-document/* keys) and Delivery Receipt (receipt/* keys). Delivery
notes from the Receipt record are also surfaced under that section.
Added `receipt` to the PoWithRelations type so the field is available
inside the component.

Fixes #10
This commit is contained in:
Claude (auto-fix) 2026-06-19 04:30:08 +05:30
parent 080dafb473
commit e2f1fa6d50

View file

@ -65,6 +65,7 @@ type PoWithRelations = {
sortOrder: number; sortOrder: number;
}[]; }[];
documents: { id: string; fileName: string; fileSize: number; storageKey: string; uploadedAt: Date }[]; documents: { id: string; fileName: string; fileSize: number; storageKey: string; uploadedAt: Date }[];
receipt: { id: string; storageKey: string; fileName: string; notes: string | null; confirmedAt: Date } | null;
actions: { id: string; actionType: string; note: string | null; metadata: import("@prisma/client").Prisma.JsonValue; createdAt: Date; actor: { name: string } }[]; actions: { id: string; actionType: string; note: string | null; metadata: import("@prisma/client").Prisma.JsonValue; createdAt: Date; actor: { name: string } }[];
}; };
@ -149,9 +150,10 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals
? "Submitter updated these line items after edits were requested. Previous values shown with strikethrough." ? "Submitter updated these line items after edits were requested. Previous values shown with strikethrough."
: "Line items were amended by manager. Current values shown; original values shown with strikethrough."; : "Line items were amended by manager. Current values shown; original values shown with strikethrough.";
const downloadUrls = await Promise.all( const downloadUrlList = await Promise.all(
po.documents.map((doc) => generateDownloadUrl(doc.storageKey)) po.documents.map((doc) => generateDownloadUrl(doc.storageKey))
); );
const urlByDocId = new Map(po.documents.map((doc, i) => [doc.id, downloadUrlList[i]]));
const canConfirmReceipt = const canConfirmReceipt =
(po.status === "PAID_DELIVERED" || po.status === "PARTIALLY_CLOSED" || po.status === "PARTIALLY_PAID") && (po.status === "PAID_DELIVERED" || po.status === "PARTIALLY_CLOSED" || po.status === "PARTIALLY_PAID") &&
@ -399,15 +401,19 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals
</div> </div>
)} )}
{/* Documents */} {/* Documents — grouped by type */}
{po.documents.length > 0 && ( {(() => {
<div className="rounded-lg border border-neutral-200 bg-white p-6"> const submissionDocs = po.documents.filter((d) => !d.storageKey.startsWith("receipt/"));
<h3 className="text-sm font-semibold text-neutral-900 mb-4">Attachments</h3> const receiptDocs = po.documents.filter((d) => d.storageKey.startsWith("receipt/"));
const hasDeliverySection = receiptDocs.length > 0 || !!po.receipt?.notes;
if (submissionDocs.length === 0 && !hasDeliverySection) return null;
const renderDocList = (docs: typeof po.documents) => (
<ul className="space-y-2"> <ul className="space-y-2">
{po.documents.map((doc, i) => ( {docs.map((doc) => (
<li key={doc.id} className="flex items-center gap-3 text-sm"> <li key={doc.id} className="flex items-center gap-3 text-sm">
<a <a
href={downloadUrls[i]} href={urlByDocId.get(doc.id)}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-primary-600 hover:underline" className="font-medium text-primary-600 hover:underline"
@ -420,8 +426,31 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals
</li> </li>
))} ))}
</ul> </ul>
</div> );
)}
return (
<div className="rounded-lg border border-neutral-200 bg-white p-6">
<h3 className="text-sm font-semibold text-neutral-900 mb-4">Attachments</h3>
<div className="space-y-5">
{submissionDocs.length > 0 && (
<div>
<p className="text-xs font-medium text-neutral-500 uppercase tracking-wide mb-2">Submission</p>
{renderDocList(submissionDocs)}
</div>
)}
{hasDeliverySection && (
<div>
<p className="text-xs font-medium text-neutral-500 uppercase tracking-wide mb-2">Delivery Receipt</p>
{po.receipt?.notes && (
<p className="text-sm text-neutral-600 italic mb-2">&ldquo;{po.receipt.notes}&rdquo;</p>
)}
{receiptDocs.length > 0 && renderDocList(receiptDocs)}
</div>
)}
</div>
</div>
);
})()}
{/* Confirm receipt CTA */} {/* Confirm receipt CTA */}
{canConfirmReceipt && ( {canConfirmReceipt && (