An admin detail view that shows only the current order status gives a misleading picture: the order is 'shipped', but was it ever in 'confirmed'? Was it cancelled and re-opened? Did the admin manually override a status? Without the full history timeline, admins cannot diagnose processing anomalies, verify that the correct notifications fired at each step, or respond to customer complaints about 'the order status changed without warning.' CWE-778 and GDPR Art. 5(1)(f) both require that the audit trail is accessible, not just stored — data in a table that no UI surfaces is operationally equivalent to data that was never captured.
Medium because order history data may exist in the database but is worthless if not surfaced in the admin UI, turning every dispute into a manual SQL query.
Add an order timeline section to the admin order detail page at app/admin/orders/[id]/page.tsx by including the history relation in the Prisma query.
// app/admin/orders/[id]/page.tsx
const order = await db.orders.findUnique({
where: { id: params.id },
include: {
items: true,
user: true,
history: { orderBy: { createdAt: 'asc' } },
},
})
// Render:
<ol>
{order.history.map((entry) => (
<li key={entry.id}>
<time>{entry.createdAt.toLocaleString()}</time>
<span>{entry.fromStatus ? `${entry.fromStatus} → ${entry.toStatus}` : entry.toStatus}</span>
{entry.note && <p>{entry.note}</p>}
{entry.actorId && <span>by {entry.actorId}</span>}
</li>
))}
</ol>
ID: ecommerce-order-management.admin-management.order-history-view
Severity: medium
What to look for: Find the admin order detail page — typically at /admin/orders/[id] or /dashboard/orders/[id]. Count the history-related data fields displayed on this page: status transitions, timestamps, actor information, and notes. At least 2 fields per history entry are required (status and timestamp). Check whether the page queries the order_history table or equivalent (see the history-log check). Enumerate how many history entries are visible for a multi-transition order. Quote the exact component or section name that renders the timeline if one exists.
Pass criteria: The admin order detail view includes a timeline or table showing all recorded state changes with their timestamps — at least 2 data fields per entry (status change and timestamp). If an actor/author is tracked in the history, it is also displayed. The history section is clearly visible without requiring the admin to navigate to a separate page. An admin detail page that shows only current status with 0 history entries rendered does not count as pass.
Fail criteria: The admin order detail page shows only the current order status, not the history (0 history entries rendered). An admin looking at the order detail cannot tell whether the order went through intermediate states. The history data may exist in the database but is not surfaced in the UI.
Skip (N/A) when: The project has no admin section. No admin order detail page exists in the codebase.
Detail on fail: "The admin order detail page at /admin/orders/[id] displays current order status, items, and totals but does not query or display order_history entries. 0 history entries are rendered. An admin cannot see when the order transitioned through previous states from this view."
Cross-reference: This check depends on the history-log check — if no history entries are written to the database, there is nothing to display. If history-log fails, this check will also fail.
Remediation: Add history display to the admin order detail page at app/admin/orders/[id]/page.tsx:
// app/admin/orders/[id]/page.tsx
async function AdminOrderDetailPage({ params }) {
const order = await db.orders.findUnique({
where: { id: params.id },
include: {
items: true,
user: true,
history: { orderBy: { createdAt: 'asc' } },
},
})
return (
<div>
{/* ... order header, items, totals ... */}
<section>
<h2>Order Timeline</h2>
<ol>
{order.history.map((entry) => (
<li key={entry.id}>
<time>{entry.createdAt.toLocaleString()}</time>
<span>
{entry.fromStatus
? `${entry.fromStatus} → ${entry.toStatus}`
: entry.toStatus}
</span>
{entry.note && <p>{entry.note}</p>}
{entry.actorId && <span>by {entry.actorId}</span>}
</li>
))}
</ol>
</section>
</div>
)
}