Fix assistant assign flow, cache staleness, and sweep KPI gloss lines
Assistant — assign:
- Order step is a dropdown, not a typed booking number. Exact bookingid /
bookingno matches now win across the whole list before any substring
fallback; row order previously decided the winner, so a short numeric id
could substring-match a different booking than the one on screen.
- Rider step resolves by id or name, so answering by typing no longer
leaves __rider undefined and throws inside executeAssign.
- notified now reflects whether the push actually went out. It was
Boolean(rider.milerprofileid) — "has an id, assume they were told" — so a
notify returning 400 still reported "The rider has been notified."
- Batch assign removed. /hub/bookings/batch-assign is a hub-console
endpoint; an admin JWT is refused (403 on an empty body, so the gate is
on identity). It read as the fast path and could never run.
Assistant — repeat runs:
- Bulk create response is read at data.results, the shape the live endpoint
returns. Only the two flatter shapes were checked, so every bookingid was
discarded while the count came out right by accident.
- Repeat carries each order's previous rider and re-assigns on create, one
admin call per order, one push per rider. Confirm cards say so.
- Message ids are clock-seeded and healed on load; the counter restarted at
1 each session and overwrote itself, giving duplicate React keys.
- Successful writes invalidate the query cache, so the page behind the panel
stops serving rows cached before the write.
Date range picker:
- Fields seed from the range already in force instead of opening blank on a
filtered table.
- Start field takes max={end}; only the end had a bound, so an inverted
range could be emitted and filtered to nothing.
UI:
- Deliveries filters moved outside the table card.
- Side nav keeps one hairline; the border and box-shadow stacked into a
heavy edge.
- KPI gloss lines removed across orders, riders, tenants, customers, hubs,
vehicles, exceptions, app users, orders summary/details, riders summary
and profitability.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { TabList, Tab } from '@astryxdesign/core/TabList';
|
||||
import { Badge } from '@astryxdesign/core/Badge';
|
||||
import { Grid } from '@astryxdesign/core/Grid';
|
||||
|
||||
import { getStatusMeta } from 'themes/dt/status';
|
||||
import SegmentCard from './SegmentCard';
|
||||
|
||||
// ==============================|| STATUS TABS ||============================== //
|
||||
// The top-level status filter strip on every list page.
|
||||
//
|
||||
// Under MUI these were hand-rolled clickable <Box> pills, because MUI's <Tabs>
|
||||
// couldn't carry a count chip or a per-tab accent. Astryx's Tab has an
|
||||
// `endContent` slot and a `selectedIcon`, so the pills are now real tabs —
|
||||
// which also means arrow-key navigation and correct `role="tab"` semantics
|
||||
// come for free instead of being missing (the old pills were plain divs with
|
||||
// onClick, which is half of Dispatch.js's outstanding a11y lint errors).
|
||||
// The top-level status filter on every list page.
|
||||
//
|
||||
// <StatusTabs
|
||||
// value={currentStatus}
|
||||
@@ -24,34 +17,75 @@ import { getStatusMeta } from 'themes/dt/status';
|
||||
// ]}
|
||||
// />
|
||||
//
|
||||
// Each tab's `status` resolves through the shared registry for its label and
|
||||
// icon, so a page only supplies the ORDER of the tabs and where each count
|
||||
// comes from. Pass `label` on an entry to override the registry wording.
|
||||
// Each entry's `status` resolves through the shared registry for its label,
|
||||
// icon, colour and hint, so a page supplies only the ORDER of the bands and
|
||||
// where each count comes from. Pass `label` on an entry to override the
|
||||
// registry wording.
|
||||
//
|
||||
// Counts render as a neutral Badge — informational, not urgent. Astryx's Badge
|
||||
// guidance reserves the loud `error` variant for counts that demand action, so
|
||||
// a page that wants that (an exceptions queue, say) passes `isUrgent` on the
|
||||
// entry rather than every count screaming by default.
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cards, not a tab strip
|
||||
// ---------------------------------------------------------------------------
|
||||
// This was a row of Astryx <Tab>s with a count badge. It is now a row of
|
||||
// <SegmentCard>s: same props, same behaviour, same registry — only the
|
||||
// rendering changed, so no page file needed touching.
|
||||
//
|
||||
// The strip was doing three jobs with one line of text: naming the band,
|
||||
// carrying its count, and being the control that selects it. As cards each of
|
||||
// those gets its own place — the name, a hint at what the band MEANS, the
|
||||
// count as the largest thing, and the band's share of the total. The selected
|
||||
// band is now legible from across the room by its coloured border rather than
|
||||
// from a 2px underline.
|
||||
//
|
||||
// The cost is honest: a card row is taller than a tab strip. That is why the
|
||||
// count is the dominant element — the row has to earn the height by being
|
||||
// readable at a glance, not just clickable.
|
||||
//
|
||||
// `size` and `hasDivider` are accepted and ignored. They were part of the
|
||||
// TabList API and several pages still pass them; silently accepting them
|
||||
// keeps those call sites working rather than spreading unknown props onto a
|
||||
// DOM node (see this folder's CLAUDE.md §7 on prop-signature changes).
|
||||
|
||||
export default function StatusTabs({ value, onChange, tabs, hideHint }) {
|
||||
// The pill under each count is the band's share of everything shown. It is
|
||||
// computed from the tabs themselves rather than taken as a prop, so it can
|
||||
// never disagree with the numbers printed above it.
|
||||
const total = tabs.reduce((sum, tab) => sum + (Number(tab.count) || 0), 0);
|
||||
|
||||
export default function StatusTabs({ value, onChange, tabs, size = 'md', hasDivider = true }) {
|
||||
return (
|
||||
<TabList value={value} onChange={onChange} size={size} hasDivider={hasDivider}>
|
||||
// width="100%" is load-bearing. DataCard renders this slot inside a VStack,
|
||||
// which aligns its children to the start rather than stretching them, so
|
||||
// without it the Grid shrinks to a single minWidth column and all eight
|
||||
// bands stack vertically in a container that had room for six across.
|
||||
<Grid
|
||||
columns={{ minWidth: 125, max: Math.max(tabs.length, 1), repeat: 'fit' }}
|
||||
gap={2}
|
||||
width="100%"
|
||||
className="dt-status-tabs-grid"
|
||||
style={{ gap: 8 }}
|
||||
>
|
||||
{tabs.map((tab) => {
|
||||
const meta = getStatusMeta(tab.status);
|
||||
const Icon = meta.icon;
|
||||
const hasCount = tab.count !== undefined && tab.count !== null;
|
||||
const count = Number(tab.count) || 0;
|
||||
// No share line when there is nothing to take a share of — "0% of 0"
|
||||
// is noise, and an empty band should simply say nothing.
|
||||
const share = total > 0 ? `${Math.round((count / total) * 100)}% of ${total}` : null;
|
||||
|
||||
return (
|
||||
<Tab
|
||||
<SegmentCard
|
||||
key={tab.status}
|
||||
value={tab.status}
|
||||
label={tab.label ?? meta.label}
|
||||
icon={<Icon size={15} />}
|
||||
endContent={hasCount ? <Badge variant={tab.isUrgent ? 'error' : 'neutral'} label={tab.count} /> : undefined}
|
||||
range={hideHint ? null : meta.hint}
|
||||
value={count}
|
||||
meta={share}
|
||||
icon={<Icon size={16} />}
|
||||
color={meta.color}
|
||||
isSelected={value === tab.status}
|
||||
onClick={() => onChange?.(tab.status)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TabList>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,6 +100,10 @@ StatusTabs.propTypes = {
|
||||
isUrgent: PropTypes.bool
|
||||
})
|
||||
).isRequired,
|
||||
/** Accepted for call-site compatibility with the previous TabList API. */
|
||||
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
||||
hasDivider: PropTypes.bool
|
||||
/** Accepted for call-site compatibility with the previous TabList API. */
|
||||
hasDivider: PropTypes.bool,
|
||||
/** If true, hides the hint text (meta.hint) on the cards. */
|
||||
hideHint: PropTypes.bool
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user