feat(orders): surface order age as colour-coded badge

Replaces the bare formatDate(order.created) string in the order card
metadata slot with an OrderAgeBadge component. The badge shows 'today'
or 'Xd old' with colour coding by bucket (green/amber/orange/red).
The absolute date is preserved in the badge's title= tooltip.

Bucket math lives in src/lib/order-age.ts (covered by 14 unit tests).
This change is rendering-only — no server or schema impact.

Note: dev-server smoke check was blocked by a pre-existing data bug
where the /api/v1/orders/list endpoint returns 0 orders across all tabs
for both admin and david.els users, despite the tab counts showing
22/18/14/17/168/0 — investigated, unrelated to this change. The
OrderAgeBadge will render correctly once that bug is fixed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Dave 2026-09-01 02:05:07 +02:00
parent 27efc8628c
commit dc8436e832

View file

@ -24,7 +24,8 @@ import {
Copy,
} from "lucide-react";
import getPocketBase from "@/lib/pocketbase";
import { displayContactName } from "@/lib/utils";
import { cn, displayContactName } from "@/lib/utils";
import { getOrderAge, bucketToBadgeClass } from "@/lib/order-age";
import { isOrderComplete } from "@/lib/server/order-completion";
import { useAuth } from "@/components/auth-provider";
import { isRepUser } from "@/lib/rep-visibility";
@ -96,6 +97,38 @@ function formatDate(dateStr: string): string {
});
}
/**
* Inline age badge for the order card metadata slot. Kept local to
* this file because it's only used here; hoist to
* `src/components/order-age-badge.tsx` if a second page needs it.
*
* The badge text conveys both the relative age ("3d old") and the
* bucket colour, and the `title=` preserves the absolute date for
* operators who want to know the exact day.
*/
function OrderAgeBadge({
created,
className,
}: {
created: string;
className?: string;
}) {
const age = getOrderAge(created);
return (
<span
data-testid={`order-age-badge-${age.bucket}`}
className={cn(
"rounded-full px-2 py-0.5 text-[10px] font-medium tabular-nums",
bucketToBadgeClass(age.bucket),
className,
)}
title={`Created ${formatDate(created)}`}
>
{age.label}
</span>
);
}
async function fetchDashboardJson<T>(url: string): Promise<T> {
const token = getPocketBase().authStore.token;
if (!token) throw new Error("Your session has expired. Please sign in again.");
@ -862,9 +895,10 @@ function OrderCard({
</span>
</>
)}
<span className="ml-auto text-xs text-muted-foreground hidden sm:inline">
{formatDate(order.created)}
</span>
<OrderAgeBadge
created={order.created}
className="ml-auto hidden sm:inline-flex"
/>
</div>
{/* Per-tab item-count summary — the operator's at-a-glance
scan line when cards are collapsed. */}