This commit is contained in:
2026-09-25 15:21:02 +05:30
parent 72f3bb0961
commit d169d24934
12 changed files with 516 additions and 28 deletions

View File

@@ -1,11 +1,13 @@
import type { ReactNode } from 'react';
import { ArrowDown, Bike, Check, MapPin, Phone, X } from 'lucide-react';
import type { DeliveryRow, OrderRow } from '@/api/types';
import type { DeliveryRow, OrderItem, OrderRow } from '@/api/types';
import { orderStage, type Stage } from './orderProgress';
import { useDeliveryMoves } from './DeliveryProgress';
import { Drawer } from './Drawer';
import { Badge, DrawerButton, DrawerCard, Metric, Metrics, Note, Row, Section } from './drawerKit';
import { branchLabel, moneyExact } from './format';
import { lineTotal, qtyLabel, summarise } from './orderItems';
import { useOrderItems } from '@/queries/hooks';
import {
DELIVERY_STATUS,
ORDER_STATUS,
@@ -135,6 +137,30 @@ function Sheet({
const value = job ? (job.deliveryamt ?? 0) : orderValue(row);
const cancelled = (status ?? '').toLowerCase().includes('cancel');
/*
* What is in the order, read once here and used twice.
*
* The three figures at the top of this sheet — value, cash to collect, items
* — came only from the row the list handed over, and the list does not carry
* them on every order: order 1151-1 opened with "Value —", "Cash to collect
* —", "Items —" while the section at the bottom of the same sheet correctly
* reported one item. Two blocks describing the same order, disagreeing,
* because one of them had asked Fiesta and the other had not.
*
* So the detail read fills the gaps. The row wins when it has a figure — it
* is the number the table behind this sheet is showing, and the two must not
* differ — and this answers only where the row was silent.
*
* One fetch: the same query key, passed down rather than called again.
*/
const detail = useOrderItems(row.orderheaderid);
const items = detail.data?.items ?? [];
const detailAmount = detail.data?.amount ?? 0;
// Units, not lines, because this metric sits beside a money figure and reads
// as "how much is in it". The Items section below names both.
const itemCount =
(job ? job.itemcount : orderQuantity(row)) || Math.round(summarise(items).units);
return (
<Drawer
title={title}
@@ -152,7 +178,13 @@ function Sheet({
Three equal columns, the shape the spec asks for and the one that
answers "what is this worth, who pays what, how much of it". */}
<Metrics>
<Metric label="Value" value={value > 0 ? moneyExact(value) : '—'} />
{/* The row's figure where it has one, Fiesta's where it does not. An
order the list reported as worth nothing is usually an order the
list was not told about, not a free one. */}
<Metric
label="Value"
value={value > 0 ? moneyExact(value) : detailAmount > 0 ? moneyExact(detailAmount) : '—'}
/>
{job ? (
<Metric
label="Rider charge"
@@ -166,11 +198,10 @@ function Sheet({
isSmall
/>
)}
<Metric
label="Items"
value={job ? String(job.itemcount ?? '—') : String(orderQuantity(row) || '—')}
isSmall
/>
{/* Counted from the lines when the row carries no count. `itemcount`
and `quantity` are both routinely absent on the list read, which is
what made this a dash on an order that plainly had something in it. */}
<Metric label="Items" value={itemCount > 0 ? String(itemCount) : '—'} isSmall />
</Metrics>
<Section title="Order journey">
@@ -262,10 +293,7 @@ function Sheet({
</Section>
) : null}
<Note>
Line items are not in the list read — Fiesta returns them from the order-detail endpoint,
which this sheet does not call yet.
</Note>
<OrderItems items={items} isLoading={detail.isLoading} isError={detail.isError} />
</Drawer>
);
}
@@ -447,3 +475,95 @@ const km = (value: string | undefined): string => {
const n = Number(value);
return Number.isFinite(n) && n > 0 ? `${n.toFixed(1)} km` : '—';
};
/* ── What is actually in the order ───────────────────────────────────────── */
/**
* The products on the order, line by line.
*
* This sheet has always been able to say what an order was WORTH and never what
* it was. The note that used to sit here said as much: Fiesta returns the
* contents from `orders/getorderdetails` and nothing called it. So an operator
* chasing a stalled delivery could see ₹840 and had to open another system to
* find out whether that was rice or ice cream — which decides whether it can
* wait an hour.
*
* Handed its data rather than fetching its own. The drawer above needs the same
* read to fill the three figures in its header, and two components asking
* separately is how the header came to say "Items —" on an order this section
* was, at that moment, listing.
*/
function OrderItems({
items,
isLoading,
isError,
}: {
items: readonly OrderItem[];
isLoading: boolean;
isError: boolean;
}) {
if (isLoading) {
return (
<Section title="Items">
<Note>Reading what is in this order…</Note>
</Section>
);
}
if (isError) {
// Not a blank section. The rest of the sheet is sound and this one read
// failed; saying so beats an empty heading that reads like an empty order.
return (
<Section title="Items">
<Note>Could not read the items on this order.</Note>
</Section>
);
}
if (items.length === 0) {
return (
<Section title="Items">
<DrawerCard tone="subtle">
<Row label="Contents" value="Fiesta returned no line items for this order." isStacked />
</DrawerCard>
</Section>
);
}
const { lines, units, short } = summarise(items);
return (
<Section
title={
// Both numbers when they differ, because they answer different
// questions: four products, twelve units. One labelled as the other is
// how a picker packs the wrong trolley.
units !== lines ? `Items · ${lines} products · ${qtyLabel(units)} units` : `Items · ${lines}`
}
>
{short > 0 ? (
<Note>
{short === 1 ? 'One line was' : `${short} lines were`} short-supplied — the shop could not
give the full quantity ordered.
</Note>
) : null}
<DrawerCard>
{items.map((item) => (
<Row
key={item.orderdetailid}
label={item.productname?.trim() || `Product ${item.productid}`}
value={
<span style={{ display: 'inline-flex', alignItems: 'baseline', gap: 10 }}>
{/* Quantity first: a picker reads this column, not the price. */}
<span style={{ color: 'var(--color-ink-2)' }}>
{qtyLabel(item.orderqty, item.unitname)}
</span>
<span style={{ fontWeight: 600 }}>{moneyExact(lineTotal(item))}</span>
</span>
}
/>
))}
</DrawerCard>
</Section>
);
}