updates on the astryx desing
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTheme, useMediaQuery } from '@mui/material';
|
||||
import {
|
||||
MdPublic,
|
||||
MdSwapHoriz,
|
||||
@@ -21,12 +20,16 @@ import {
|
||||
MdHourglassBottom
|
||||
} from 'react-icons/md';
|
||||
import {
|
||||
onActivate,
|
||||
useIsNarrowViewport,
|
||||
stepColor,
|
||||
getStatusStyle,
|
||||
FINAL_STATUSES,
|
||||
SKIPPED_STATUSES,
|
||||
ordinal
|
||||
} from './dispatchShared';
|
||||
import StatusBadge from 'components/nearle_components/StatusBadge';
|
||||
import { STATUS } from 'themes/dt/tokens';
|
||||
|
||||
|
||||
// Right-side data panel rendered in Compare mode. Pure presentation +
|
||||
// memoized derivations: feed it the comparison state from Dispatch and
|
||||
@@ -65,8 +68,7 @@ function CompareDataPanel({
|
||||
// scoped media-query block in Dispatch.css uses to stack the side-by-side
|
||||
// timing/clock columns vertically and let stat chips wrap). No data,
|
||||
// handler, or derivation behaviour changes on any breakpoint.
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const isMobile = useIsNarrowViewport();
|
||||
|
||||
// All derivations live in a single useMemo so the cost of re-running
|
||||
// them is paid only when an upstream input actually changes — not on
|
||||
@@ -105,7 +107,13 @@ function CompareDataPanel({
|
||||
(sum.onTime / onTimeForScore) * 25 +
|
||||
((totalForScore - sum.anomalies) / totalForScore) * 15
|
||||
);
|
||||
const scoreColor = score >= 85 ? '#16a34a' : score >= 65 ? '#f59e0b' : '#dc2626';
|
||||
// A compliance score is a health reading, not a lifecycle status, so it
|
||||
// must NOT go through StatusBadge — it reads the raw hexes from the STATUS
|
||||
// palette, which is the sanctioned "arbitrary accent" channel (root
|
||||
// CLAUDE.md §6.3). Thresholds (85/65) and the ring mechanism below are
|
||||
// deliberately unchanged; only the three constants are retargeted, so a QA
|
||||
// A/B comparison should show identical scores and bands.
|
||||
const scoreColor = score >= 85 ? STATUS.success : score >= 65 ? STATUS.warning : STATUS.error;
|
||||
const scoreLabel = score >= 85 ? 'Excellent' : score >= 65 ? 'Acceptable' : 'Needs review';
|
||||
|
||||
// KPIs derived from delivery timestamps.
|
||||
@@ -253,7 +261,11 @@ function CompareDataPanel({
|
||||
className={`cdp-seq-diff${
|
||||
focusedCompareStep === d.sequenceStep ? ' is-focused' : ''
|
||||
}${focusable ? '' : ' is-nested'}`}
|
||||
role="treeitem"
|
||||
aria-selected={focusedCompareStep === d.sequenceStep}
|
||||
tabIndex={0}
|
||||
onClick={() => focusStep(d.sequenceStep)}
|
||||
onKeyDown={onActivate(() => focusStep(d.sequenceStep))}
|
||||
>
|
||||
<span
|
||||
className="cdp-seq-diff-num"
|
||||
@@ -433,7 +445,9 @@ function CompareDataPanel({
|
||||
<div
|
||||
className="cdp-section-head cdp-section-head-clickable"
|
||||
onClick={() => setSequenceOpen((v) => !v)}
|
||||
onKeyDown={onActivate(() => setSequenceOpen((v) => !v))}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-expanded={sequenceOpen}
|
||||
title={sequenceOpen ? 'Collapse route sequence' : 'Expand route sequence'}
|
||||
>
|
||||
@@ -455,7 +469,7 @@ function CompareDataPanel({
|
||||
{sequenceOpen && (
|
||||
<div className="cdp-seq">
|
||||
{outOfOrderSteps.length > 0 ? (
|
||||
<ul className="cdp-seq-diffs">
|
||||
<ul className="cdp-seq-diffs" role="tree" aria-label="Route sequence differences">
|
||||
{seqRuns.map((run, runIdx) => {
|
||||
if (run.items.length === 1) {
|
||||
return renderDiffRow(run.items[0]);
|
||||
@@ -472,7 +486,11 @@ function CompareDataPanel({
|
||||
<React.Fragment key={`run-${runIdx}-${first.d.sequenceStep}`}>
|
||||
<li
|
||||
className={`cdp-seq-diff is-group${isOpen ? ' is-expanded' : ''}${groupFocused ? ' is-focused' : ''}`}
|
||||
role="treeitem"
|
||||
aria-selected={groupFocused}
|
||||
tabIndex={0}
|
||||
onClick={() => toggleSeqGroup(runIdx)}
|
||||
onKeyDown={onActivate(() => toggleSeqGroup(runIdx))}
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<span className="cdp-seq-group-num">
|
||||
@@ -508,8 +526,8 @@ function CompareDataPanel({
|
||||
</span>
|
||||
</li>
|
||||
{isOpen && (
|
||||
<li className="cdp-seq-group-children-wrap">
|
||||
<ul className="cdp-seq-group-children">
|
||||
<li className="cdp-seq-group-children-wrap" role="presentation">
|
||||
<ul className="cdp-seq-group-children" role="group">
|
||||
{run.items.map((it) => renderDiffRow(it, false))}
|
||||
</ul>
|
||||
</li>
|
||||
@@ -670,7 +688,9 @@ function CompareDataPanel({
|
||||
<div
|
||||
className="cdp-highlight is-best"
|
||||
onClick={() => focusStep(bestStep.sequenceStep)}
|
||||
onKeyDown={onActivate(() => focusStep(bestStep.sequenceStep))}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Focus this step"
|
||||
>
|
||||
<span className="cdp-highlight-rail" aria-hidden="true" />
|
||||
@@ -710,7 +730,9 @@ function CompareDataPanel({
|
||||
<div
|
||||
className="cdp-highlight is-worst"
|
||||
onClick={() => focusStep(worstStep.sequenceStep)}
|
||||
onKeyDown={onActivate(() => focusStep(worstStep.sequenceStep))}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Focus this step"
|
||||
>
|
||||
<span className="cdp-highlight-rail" aria-hidden="true" />
|
||||
@@ -810,7 +832,7 @@ function CompareDataPanel({
|
||||
? 'is-under'
|
||||
: ''
|
||||
: '';
|
||||
const statusStyle = getStatusStyle(focused.orderstatus);
|
||||
|
||||
const profit = parseFloat(focused.order?.profit || 0);
|
||||
return (
|
||||
<section className="cdp-section">
|
||||
@@ -845,14 +867,7 @@ function CompareDataPanel({
|
||||
Order #{focused.orderid}
|
||||
</div>
|
||||
</div>
|
||||
{focused.orderstatus && (
|
||||
<span
|
||||
className="compare-delta-status"
|
||||
style={{ background: statusStyle.bg, color: statusStyle.fg }}
|
||||
>
|
||||
{statusStyle.label}
|
||||
</span>
|
||||
)}
|
||||
{focused.orderstatus && <StatusBadge status={focused.orderstatus} />}
|
||||
</div>
|
||||
<div className="compare-delta-grid">
|
||||
<div className={`compare-delta-cell${focused.anomaly ? ' is-anomaly' : ''}`}>
|
||||
@@ -920,7 +935,7 @@ function CompareDataPanel({
|
||||
Deviations ({deviations.length})
|
||||
</span>
|
||||
</div>
|
||||
<ul className="cdp-dev-list">
|
||||
<ul className="cdp-dev-list" role="listbox" aria-label="Step deviations">
|
||||
{deviations.map((d) => {
|
||||
const color = stepColor(d.sequenceStep - 1);
|
||||
const kmSign = d.kmDelta >= 0 ? '+' : '';
|
||||
@@ -928,7 +943,11 @@ function CompareDataPanel({
|
||||
<li
|
||||
key={`dev-${d.sequenceStep}`}
|
||||
className={`cdp-dev-item${focusedCompareStep === d.sequenceStep ? ' is-focused' : ''}`}
|
||||
role="option"
|
||||
aria-selected={focusedCompareStep === d.sequenceStep}
|
||||
tabIndex={0}
|
||||
onClick={() => focusStep(d.sequenceStep)}
|
||||
onKeyDown={onActivate(() => focusStep(d.sequenceStep))}
|
||||
>
|
||||
<span className="cdp-dev-num" style={{ background: color }}>
|
||||
{d.sequenceStep}
|
||||
@@ -970,7 +989,7 @@ function CompareDataPanel({
|
||||
{delivered}/{compareDeltas.length} delivered
|
||||
</span>
|
||||
</div>
|
||||
<ul className="cdp-step-list">
|
||||
<ul className="cdp-step-list" role="listbox" aria-label="Route steps">
|
||||
{compareDeltas.map((d) => {
|
||||
const color = stepColor(d.sequenceStep - 1);
|
||||
const statusLow = String(d.orderstatus || '').toLowerCase();
|
||||
@@ -978,7 +997,7 @@ function CompareDataPanel({
|
||||
const isSkipped = SKIPPED_STATUSES.has(statusLow);
|
||||
const isCorrect = isDelivered && !d.anomaly;
|
||||
const isFocused = focusedCompareStep === d.sequenceStep;
|
||||
const statusStyle = getStatusStyle(d.orderstatus);
|
||||
|
||||
const profit = parseFloat(d.order?.profit || 0);
|
||||
const kmSign = d.kmDelta >= 0 ? '+' : '';
|
||||
const timeCls =
|
||||
@@ -1001,7 +1020,11 @@ function CompareDataPanel({
|
||||
<li
|
||||
key={`step-${d.sequenceStep}`}
|
||||
className={stepCls}
|
||||
role="option"
|
||||
aria-selected={isFocused}
|
||||
tabIndex={0}
|
||||
onClick={() => focusStep(d.sequenceStep)}
|
||||
onKeyDown={onActivate(() => focusStep(d.sequenceStep))}
|
||||
>
|
||||
<span className="cdp-step-num" style={{ background: color }}>
|
||||
{d.sequenceStep}
|
||||
@@ -1021,14 +1044,7 @@ function CompareDataPanel({
|
||||
<span className="cdp-step-title">
|
||||
{d.deliverycustomer || `Step ${d.sequenceStep}`}
|
||||
</span>
|
||||
{d.orderstatus && (
|
||||
<span
|
||||
className="cdp-step-status"
|
||||
style={{ background: statusStyle.bg, color: statusStyle.fg }}
|
||||
>
|
||||
{statusStyle.label}
|
||||
</span>
|
||||
)}
|
||||
{d.orderstatus && <StatusBadge status={d.orderstatus} />}
|
||||
</div>
|
||||
<div className="cdp-step-sub">
|
||||
{d.pickupcustomer ? `from ${d.pickupcustomer} · ` : ''}
|
||||
|
||||
Reference in New Issue
Block a user