orders: restore the design the prototype had, and stop repeating the row
ci / lint-test (push) Successful in 49s

Rebuilding the prototype in React quietly dropped most of what made it read
well. Side by side, the live page had lost the year strip entirely — which was
both the hero and the context for the date range — along with the masthead
totals, the A$ prefix on amounts, and the Fraunces wordmark. The trust marker
rendered as a tofu box. Restored, with the strip clickable so a bar scopes to
that year.

Also three content fixes, all visible the moment anyone looked at a real page.

The title and the manifest were printing the same text twice on 1,964 of 4,649
itemised rows — 42% — because on a single-item order the order title IS the line
item. The title now earns its line only when it says something the items do not.

Food line items carry the full customisation: one Subway order runs to 450
characters naming every topping, and it swamped everything around it. Rows show
a short form, cut at the first bracket where there is one, since "Footlong
(Italian Herb…" is the product and the bracket is the customisation.

Rows now expand, following the disclosure pattern the transactions page already
uses, and the query fetches twelve items rather than three so opening one costs
no round trip.

Merchant names get a presentational tidy — corporate suffixes dropped, a
lower-cased domain capitalised. This never merges two merchants: the estate
holds amazon.com.au, Amazon.in and Amazon Services Australia as three distinct
entities, and Amazon.in must stay separate because it is a different marketplace
rather than a name variant. Actually unifying them is the merchant-alias bridge.
This commit is contained in:
2026-08-12 13:28:06 +10:00
parent c05c4b5a35
commit 1b34cd65d8
2 changed files with 242 additions and 31 deletions
+31 -3
View File
@@ -229,7 +229,10 @@ export async function getOrderFeed(filters: OrderFilters) {
CASE WHEN jsonb_typeof(f.details->'line_items') = 'array'
THEN f.details->'line_items' ELSE '[]'::jsonb END)
WITH ORDINALITY t(li, n)
WHERE t.n <= 3) AS item_preview
-- 12, not 3: the row shows the first few and the expansion shows
-- the rest without a second round trip. Beyond 12 the detail page
-- is the right surface.
WHERE t.n <= 12) AS item_preview
${FROM_CLAUSE}
${where}
-- NULLS LAST always: eta_date is NULL on ~65% of retail and ~95% of food,
@@ -244,6 +247,14 @@ export async function getOrderFeed(filters: OrderFilters) {
}
export interface OrderFacets {
/** Orders per calendar year across the WHOLE feed — deliberately not scoped
* to the active date range, because the strip's job is to show where the
* current range sits in the twenty-one years available. */
years: { year: number; n: number }[];
/** Corpus totals for the masthead, so a filtered count is never mistaken
* for the whole archive. */
all_time: number;
with_amount: number;
lanes: { lane: string; n: number }[];
platforms: { platform: string; n: number }[];
statuses: { status: string; n: number }[];
@@ -264,7 +275,19 @@ export async function getOrderFacets(filters: OrderFilters): Promise<OrderFacets
const { where, params } = buildWhere(base);
const laneScoped = buildWhere({ ...base, lane: filters.lane });
const [lanes, platforms, statuses, currencies] = await Promise.all([
// The year strip and the all-time totals ignore the date range on purpose.
const spanFilters: OrderFilters = { hide_lifecycle_only: filters.hide_lifecycle_only };
const span = buildWhere(spanFilters);
const [years, totals, lanes, platforms, statuses, currencies] = await Promise.all([
queryRaw<{ year: number; n: number }>(
`SELECT extract(year from f.ordered_at)::int AS year, count(*)::int AS n
${FROM_CLAUSE} ${span.where}${span.where ? " AND" : " WHERE"} f.ordered_at IS NOT NULL
GROUP BY 1 ORDER BY 1`, span.params),
queryRaw<{ all_time: number; with_amount: number }>(
`SELECT count(*)::int AS all_time,
count(*) FILTER (WHERE f.order_total IS NOT NULL AND f.order_total > 0)::int AS with_amount
${FROM_CLAUSE} ${span.where}`, span.params),
queryRaw<{ lane: string; n: number }>(
`SELECT f.lane, count(*)::int AS n ${FROM_CLAUSE} ${where} GROUP BY 1 ORDER BY 2 DESC`, params),
queryRaw<{ platform: string; n: number }>(
@@ -277,7 +300,12 @@ export async function getOrderFacets(filters: OrderFilters): Promise<OrderFacets
`SELECT f.currency, count(*)::int AS n ${FROM_CLAUSE} ${laneScoped.where}
GROUP BY 1 ORDER BY 2 DESC`, laneScoped.params),
]);
return { lanes, platforms, statuses, currencies };
return {
years,
all_time: totals[0]?.all_time ?? 0,
with_amount: totals[0]?.with_amount ?? 0,
lanes, platforms, statuses, currencies,
};
}
export interface OrderLifecycleEvent {