feat(po): show note author name on manager note banners
Derives the author from the most recent EDITS_REQUESTED / REJECTED / APPROVED action that carries a note. PO detail banner now shows 'Note from [name]', edit-page banner shows 'Edits requested by [name]', and the closed-orders list prefixes the truncated note with the author's name. No schema changes required - uses the already-fetched actions with actor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c92f136b09
commit
3e5d11c4ae
4 changed files with 36 additions and 6 deletions
|
|
@ -24,6 +24,15 @@ export default async function MyOrdersPage() {
|
|||
include: {
|
||||
vessel: { select: { name: true } },
|
||||
account: { select: { name: true, code: true } },
|
||||
actions: {
|
||||
where: {
|
||||
actionType: { in: ["EDITS_REQUESTED", "REJECTED", "APPROVED", "APPROVED_WITH_NOTE"] },
|
||||
note: { not: null },
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 1,
|
||||
select: { actor: { select: { name: true } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -62,6 +71,7 @@ type PoRow = {
|
|||
account: { name: string; code: string };
|
||||
updatedAt: Date;
|
||||
managerNote: string | null;
|
||||
actions: { actor: { name: string } }[];
|
||||
};
|
||||
|
||||
function PoTable({ title, rows, className = "" }: { title: string; rows: PoRow[]; className?: string }) {
|
||||
|
|
@ -95,7 +105,7 @@ function PoTable({ title, rows, className = "" }: { title: string; rows: PoRow[]
|
|||
</Link>
|
||||
{po.managerNote && (
|
||||
<p className="mt-0.5 text-xs text-warning-700 italic truncate max-w-xs">
|
||||
Note: {po.managerNote}
|
||||
{po.actions[0]?.actor.name ? `${po.actions[0].actor.name}: ` : "Note: "}{po.managerNote}
|
||||
</p>
|
||||
)}
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -37,9 +37,10 @@ interface Props {
|
|||
vessels: Vessel[];
|
||||
accounts: Account[];
|
||||
vendors: Vendor[];
|
||||
managerNoteAuthor?: string | null;
|
||||
}
|
||||
|
||||
export function EditPoForm({ po, vessels, accounts, vendors }: Props) {
|
||||
export function EditPoForm({ po, vessels, accounts, vendors, managerNoteAuthor }: Props) {
|
||||
const router = useRouter();
|
||||
const [lineItems, setLineItems] = useState<LineItemInput[]>(
|
||||
po.lineItems.map((li) => ({
|
||||
|
|
@ -104,7 +105,9 @@ export function EditPoForm({ po, vessels, accounts, vendors }: Props) {
|
|||
<form id="edit-po-form" className="space-y-6" onSubmit={(e) => e.preventDefault()}>
|
||||
{canResubmit && (
|
||||
<div className="rounded-lg border border-warning-100 bg-warning-50 px-4 py-3">
|
||||
<p className="text-sm font-medium text-warning-700">Edits requested</p>
|
||||
<p className="text-sm font-medium text-warning-700">
|
||||
{managerNoteAuthor ? `Edits requested by ${managerNoteAuthor}` : "Edits requested"}
|
||||
</p>
|
||||
{po.managerNote && (
|
||||
<p className="mt-1 text-sm text-warning-700 italic">"{po.managerNote}"</p>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -29,10 +29,17 @@ export default async function EditPoPage({ params }: Props) {
|
|||
po.submitterId === session.user.id || session.user.role === "SUPERUSER";
|
||||
if (!canEdit) redirect(`/po/${id}`);
|
||||
|
||||
const [vessels, accounts, vendors] = await Promise.all([
|
||||
const [vessels, accounts, vendors, noteAction] = await Promise.all([
|
||||
db.vessel.findMany({ where: { isActive: true }, orderBy: { name: "asc" } }),
|
||||
db.account.findMany({ where: { isActive: true }, orderBy: { name: "asc" } }),
|
||||
db.vendor.findMany({ where: { isActive: true }, orderBy: { name: "asc" } }),
|
||||
po.status === "EDITS_REQUESTED"
|
||||
? db.pOAction.findFirst({
|
||||
where: { poId: po.id, actionType: "EDITS_REQUESTED", note: { not: null } },
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: { actor: { select: { name: true } } },
|
||||
})
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
|
||||
const serializedPo = {
|
||||
|
|
@ -53,7 +60,7 @@ export default async function EditPoPage({ params }: Props) {
|
|||
<h1 className="text-2xl font-semibold text-neutral-900">Edit Purchase Order</h1>
|
||||
<p className="mt-1 text-sm text-neutral-500 font-mono">{po.poNumber}</p>
|
||||
</div>
|
||||
<EditPoForm po={serializedPo} vessels={vessels} accounts={accounts} vendors={vendors} />
|
||||
<EditPoForm po={serializedPo} vessels={vessels} accounts={accounts} vendors={vendors} managerNoteAuthor={noteAction?.actor.name ?? null} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,14 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals
|
|||
.reverse()
|
||||
.find((a) => a.actionType === "MANAGER_LINE_EDIT");
|
||||
|
||||
const noteAction = [...po.actions]
|
||||
.reverse()
|
||||
.find((a) =>
|
||||
["EDITS_REQUESTED", "REJECTED", "APPROVED", "APPROVED_WITH_NOTE"].includes(a.actionType) &&
|
||||
a.note
|
||||
);
|
||||
const managerNoteAuthor = noteAction?.actor.name ?? null;
|
||||
|
||||
// Resubmit snapshot: stored in the most recent SUBMITTED action's metadata
|
||||
// when the submitter resubmits after EDITS_REQUESTED.
|
||||
type ResubmitSnapshot = {
|
||||
|
|
@ -208,7 +216,9 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals
|
|||
{/* Manager note banner */}
|
||||
{po.managerNote && (
|
||||
<div className="rounded-lg border border-warning-100 bg-warning-50 px-4 py-3">
|
||||
<p className="text-sm font-medium text-warning-700 mb-0.5">Manager note</p>
|
||||
<p className="text-sm font-medium text-warning-700 mb-0.5">
|
||||
{managerNoteAuthor ? `Note from ${managerNoteAuthor}` : "Manager note"}
|
||||
</p>
|
||||
<p className="text-sm text-warning-700">{po.managerNote}</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue