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:
parent
080dafb473
commit
e2f1fa6d50
1 changed files with 38 additions and 9 deletions
|
|
@ -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">“{po.receipt.notes}”</p>
|
||||||
|
)}
|
||||||
|
{receiptDocs.length > 0 && renderDocList(receiptDocs)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
{/* Confirm receipt CTA */}
|
{/* Confirm receipt CTA */}
|
||||||
{canConfirmReceipt && (
|
{canConfirmReceipt && (
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue