From 0f8510a3ea2ba9df93f62896e46d50bac916758a Mon Sep 17 00:00:00 2001 From: dharaneesh-r Date: Fri, 21 Aug 2026 17:24:54 +0530 Subject: [PATCH] updates on the ai bot and the deliveries page design --- .../nearle/assistant/DoormileAI/AIPanel.js | 33 ++++++++++++++----- .../assistant/DoormileAI/pageContext.js | 11 +++++++ src/pages/nearle/deliveries/deliveries.js | 19 ++++++++--- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/pages/nearle/assistant/DoormileAI/AIPanel.js b/src/pages/nearle/assistant/DoormileAI/AIPanel.js index a17087e..ab0bb7e 100644 --- a/src/pages/nearle/assistant/DoormileAI/AIPanel.js +++ b/src/pages/nearle/assistant/DoormileAI/AIPanel.js @@ -26,7 +26,7 @@ import { startAssignFlow, answerAssignStep } from '../assignFlow'; import { executeAssign, executeRepeatAssign, describeRider } from '../assignActions'; import { startRepeatFlow, answerRepeatStep } from '../repeatFlow'; import { buildRepeatRun, describeDay } from '../repeatRuns'; -import { getPageContext, getFollowUps, toChips, ORDER_CREATED } from './pageContext'; +import { getPageContext, getFollowUps, toChips, ORDER_CREATED, ORDER_CREATED_ASSIGNED } from './pageContext'; import { Spark, LiveIndicator, TypingIndicator } from './AIParts'; import AIWelcome, { SuggestionChips } from './AIWelcome'; import AIMessage from './AIMessage'; @@ -1133,9 +1133,6 @@ const AIPanel = ({ isOpen, onClose }) => { status: res.failed && !res.created ? 'failed' : 'done', error: res.failed ? `${res.failed} row(s) failed` : undefined }); - // Partial success still means orders exist and need riders. Only a run - // where nothing landed leaves nothing to assign. - if (res.created > 0) setOutcomeKey(ORDER_CREATED); // A repeat carries yesterday's rider on each row, so the new copies go // straight back to whoever ran them — no second question. Rows with no // previous rider simply stay pending, and a CSV import has none at all, @@ -1145,14 +1142,23 @@ const AIPanel = ({ isOpen, onClose }) => { : null; if (repeatAssign?.assigned) refreshPageData(); - // Remember exactly which ones, so "Assign order" can act on them — - // minus any that just got a rider automatically. + // Which of the new orders still need a rider — the ones whose source + // had none, plus any the re-assign could not place. + const failedIds = new Set((repeatAssign?.failures || []).map((f) => f.bookingid)); const stillUnassigned = repeatAssign ? (res.createdPairs || []) - .filter(({ index }) => rows?.[index]?.__previousMilerUserId == null) + .filter(({ index, bookingid }) => rows?.[index]?.__previousMilerUserId == null || failedIds.has(bookingid)) .map((p) => p.bookingid) : res.createdIds; if (stillUnassigned?.length) setLastCreated({ ids: stillUnassigned, booking: null }); + + // Set AFTER the assign, because which follow-ups make sense depends on + // whether anything is still waiting for a rider. A run where every + // order was placed automatically must not then suggest "Assign order" — + // that offers finished work as a pending step. + if (res.created > 0) { + setOutcomeKey(repeatAssign?.assigned && !stillUnassigned.length ? ORDER_CREATED_ASSIGNED : ORDER_CREATED); + } push({ sender: 'assistant', text: `Created ${res.created} of ${rows.length} order${rows.length === 1 ? '' : 's'}.`, @@ -1170,10 +1176,21 @@ const AIPanel = ({ isOpen, onClose }) => { // Stated plainly, including what was NOT done. An operator who // believes all 13 went back to their riders will not check the // three that stayed pending. + // Assignment and notification are reported separately, because + // they fail separately. This line used to end "notified + // automatically" on the strength of `assigned` alone — so a notify + // returning 500 (seen live) was still announced as a delivered + // push. Same mistake executeAssign already had; it does not get to + // survive here just because the wording is friendlier. repeatAssign?.assigned ? `${repeatAssign.assigned} went straight back to yesterday's rider across ${repeatAssign.riders} rider${ repeatAssign.riders === 1 ? '' : 's' - }, notified automatically.` + }.` + : null, + repeatAssign?.assigned + ? repeatAssign.notified === repeatAssign.riders + ? `${repeatAssign.notified === 1 ? 'That rider was' : 'Those riders were'} notified.` + : `${repeatAssign.riders - repeatAssign.notified} of ${repeatAssign.riders} could not be notified — tell them directly. The orders ARE assigned.` : null, repeatAssign?.skipped ? `${repeatAssign.skipped} had no rider yesterday and are waiting for one.` : null, repeatAssign?.failures?.length diff --git a/src/pages/nearle/assistant/DoormileAI/pageContext.js b/src/pages/nearle/assistant/DoormileAI/pageContext.js index dbb3ca1..557e42c 100644 --- a/src/pages/nearle/assistant/DoormileAI/pageContext.js +++ b/src/pages/nearle/assistant/DoormileAI/pageContext.js @@ -88,6 +88,15 @@ const REPEAT_TEXT = "Repeat yesterday's orders"; // side of the write. export const ORDER_CREATED = '__orderCreated'; +// The same write, but with every new order already given a rider — a repeat run +// that handed each copy back to whoever ran it yesterday. +// +// It needs its own key purely so ASSIGN_TEXT can be left out. Offering "Assign +// order" after the bot has just assigned them all presents finished work as a +// pending step, and an operator who takes the suggestion is walked into +// reassigning orders that are already out. +export const ORDER_CREATED_ASSIGNED = '__orderCreatedAssigned'; + const ORDERS = { label: 'Orders', suggestions: [ @@ -230,6 +239,8 @@ const FOLLOW_UPS = { 'Create a customer': ['Create an order', 'How many customers do we have?'], [ASSIGN_TEXT]: ['How many pending orders today?', 'Which orders are delayed?', 'How many riders are active?'], [ORDER_CREATED]: [ASSIGN_TEXT, 'How many pending orders today?', 'Which orders are delayed?', 'Create an order'], + // No ASSIGN_TEXT: there is nothing left to assign. + [ORDER_CREATED_ASSIGNED]: ['How many riders are active?', 'How many pending orders today?', 'Which orders are delayed?', 'Create an order'], // --- order volume --- "Give me today's operations summary": [ diff --git a/src/pages/nearle/deliveries/deliveries.js b/src/pages/nearle/deliveries/deliveries.js index 8922981..4d218a9 100644 --- a/src/pages/nearle/deliveries/deliveries.js +++ b/src/pages/nearle/deliveries/deliveries.js @@ -867,25 +867,36 @@ const Deliveries = () => { ); + // hasTruncateTooltip={false} on every Text in this cell. + // + // Astryx's Text turns its own truncation tooltip ON by default as + // soon as maxLines > 0, so each of these four lines was offering to + // re-show its own clipped string on hover. Nested inside the route + // Tooltip, that means hovering the address pops a bare address over + // the pickup/drop card that already contains it — two tooltips + // racing to explain the same cell. + // + // The route tooltip carries the full pickup AND drop address, so + // nothing is lost by silencing the per-line ones. return ( Pickup - + {r.pickupcustomer || 'Pickup'} - + {pickupAddr ? `· ${pickupAddr}` : ''} Drop - + {r.deliverycustomer || 'Drop'} - + {dropAddr ? `· ${dropAddr}` : ''}