From e34e41ca96ca6ab67d0600d824a9ff37d02c62ee Mon Sep 17 00:00:00 2001 From: dharaneesh-r Date: Thu, 27 Aug 2026 13:11:05 +0530 Subject: [PATCH] updates on the ai bot and the queries and the delivereis page --- src/api/doormile/queries.js | 35 +++-- src/components/assistant/DoormileAI.css | 32 ++-- .../assistant/DoormileAI/AIFlowStep.jsx | 40 +++-- .../assistant/DoormileAI/AIMessage.jsx | 50 +++--- .../assistant/DoormileAI/AIRowsStep.jsx | 144 +++++++++--------- src/lib/ZoneContext.jsx | 23 +++ src/lib/assistant/DoormileAI.css | 32 ++-- src/pages/doormile/deliveries/Deliveries.jsx | 5 +- 8 files changed, 217 insertions(+), 144 deletions(-) diff --git a/src/api/doormile/queries.js b/src/api/doormile/queries.js index 7d26ad6..78025e8 100644 --- a/src/api/doormile/queries.js +++ b/src/api/doormile/queries.js @@ -87,6 +87,7 @@ const BOOKING_STATUS_TO_DELIVERY_STATUS = { arrived: 'arrived', converted_to_consignment: 'picked', collected_by_miler: 'picked', + collected: 'picked', picked: 'picked', out_for_delivery: 'active', in_transit: 'active', @@ -115,7 +116,7 @@ const BOOKING_STATUS_TO_DELIVERY_STATUS = { export const deriveDeliveryStatus = (bookingOrStatus, consignmentMapOrReachedAt) => { if (!bookingOrStatus) return 'pending'; - // If passed a raw status string + // If passed a simple status string if (typeof bookingOrStatus === 'string') { const raw = bookingOrStatus.trim().toLowerCase(); if (raw === 'pickup_scheduled') { @@ -134,27 +135,29 @@ export const deriveDeliveryStatus = (bookingOrStatus, consignmentMapOrReachedAt) const cStatus = consignmentStatusFor(b, typeof consignmentMapOrReachedAt === 'object' && consignmentMapOrReachedAt instanceof Map ? consignmentMapOrReachedAt : undefined); const reached = b.reachedat ?? b.reached_at ?? b.reachedAt ?? b.reachedtime ?? b.reached_time; - // If booking is Converted_To_Consignment, switch directly to consignmentstatus: - // Converted_To_Consignment is a handoff marker, not a display state. - if (rawBookingStatus === 'converted_to_consignment') { - if (cStatus) { - return mapBookingStatusToDeliveryStatus(cStatus); - } + // 1. If consignment status is present, it defines the live state after pickup: + // - 'collected_by_miler' / 'collected' / 'picked' -> 'picked' (Picked tab) + // - 'out_for_delivery' / 'in_transit' / 'active' -> 'active' (Active tab, rider started delivery) + // - 'delivered' -> 'delivered' (Delivered tab) + // - 'cancelled' -> 'cancelled' (Cancelled tab) + if (cStatus) { + const rawC = String(cStatus).trim().toLowerCase(); + const mappedC = BOOKING_STATUS_TO_DELIVERY_STATUS[rawC]; + if (mappedC) return mappedC; + } + + // 2. Booking level handoff states: + if (rawBookingStatus === 'converted_to_consignment' || rawBookingStatus === 'collected_by_miler' || rawBookingStatus === 'picked') { return 'picked'; } - // If consignment status is present, it wins: - if (cStatus) { - return mapBookingStatusToDeliveryStatus(cStatus); - } - - // Before pickup (booking lifecycle): + // 3. Before pickup (booking lifecycle): if (rawBookingStatus === 'pickup_scheduled') { const isReached = reached != null && reached !== false && reached !== ''; return isReached ? 'arrived' : 'accepted'; } - return mapBookingStatusToDeliveryStatus(rawBookingStatus, reached); + return BOOKING_STATUS_TO_DELIVERY_STATUS[rawBookingStatus] || rawBookingStatus; }; export const mapBookingStatusToDeliveryStatus = (statusOrRow, maybeReachedAt) => { @@ -852,8 +855,8 @@ export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => { // Pickup_Scheduled + reachedat present -> Arrived // Pickup_Scheduled + reachedat absent -> Accepted orderstatus: effectiveDeliveryStatus, - consignmentstatus: cStatus, - statusfromconsignment: cStatus != null || String(b.status || '').toLowerCase() === 'converted_to_consignment', + consignmentstatus: cStatus || effectiveDeliveryStatus, + statusfromconsignment: Boolean(cStatus && !['accepted', 'pending', 'arrived'].includes(effectiveDeliveryStatus)), droplat: b.deliverylatitude, droplon: b.deliverylongitude }; diff --git a/src/components/assistant/DoormileAI.css b/src/components/assistant/DoormileAI.css index 745eed3..36d46f3 100644 --- a/src/components/assistant/DoormileAI.css +++ b/src/components/assistant/DoormileAI.css @@ -953,40 +953,50 @@ body.dai-docked .astryx-layout-content { } } /* -------------------------------------------------------------------------- - Write-action confirm card + Write-action confirm card & buttons -------------------------------------------------------------------------- */ .dai-root .dai-action { - padding: 10px 11px; - border: 1px solid var(--dai-border-strong); - border-radius: 5px; - background: var(--dai-surface-alt); + padding: 12px 14px; + border: 1px solid #e2e8f0; + border-radius: 12px; + background: #ffffff; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02); + display: flex; + flex-direction: column; + gap: 10px; } .dai-root .dai-action-icon { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; - width: 26px; - height: 26px; - border-radius: 5px; + width: 28px; + height: 28px; + border-radius: 8px; color: #4f46e5; background: rgba(99, 102, 241, 0.1); } .dai-root .dai-action-summary { font-size: 13px; - line-height: 1.35; - font-weight: 550; + line-height: 1.4; + font-weight: 600; color: var(--dai-text); } .dai-root .dai-action-note { font-size: 12px; - color: var(--dai-text-secondary); + color: #64748b; + line-height: 1.4; } .dai-root .dai-ok { color: #047857; + font-weight: 500; } .dai-root .dai-err { color: #b91c1c; + font-weight: 500; +} +.dai-root .dai-action button { + cursor: pointer; } /* -------------------------------------------------------------------------- Overflow containment diff --git a/src/components/assistant/DoormileAI/AIFlowStep.jsx b/src/components/assistant/DoormileAI/AIFlowStep.jsx index d3f0ad4..6ccdc70 100644 --- a/src/components/assistant/DoormileAI/AIFlowStep.jsx +++ b/src/components/assistant/DoormileAI/AIFlowStep.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { VStack } from '@astryxdesign/core/VStack'; import { HStack } from '@astryxdesign/core/HStack'; import { Text } from '@astryxdesign/core/Text'; -import { Button } from '@astryxdesign/core/Button'; +import { Button } from '@/components/ui/button'; import { Selector } from '@astryxdesign/core/Selector'; // ==============================|| Doormile AI — one dropdown step ||============================== // @@ -70,7 +70,16 @@ const AIFlowStep = ({ step, onChoose, onCancel, draft, isBusy }) => { ? 'This tenant has no saved business locations — add one on the Tenants page before creating an order.' : step.emptyHint || 'There’s nothing to choose from here.'} - + ); } @@ -90,19 +99,26 @@ const AIFlowStep = ({ step, onChoose, onCancel, draft, isBusy }) => { hasSearch={options.length > 8} isDisabled={isBusy} /> - +
+ +
); }; diff --git a/src/components/assistant/DoormileAI/AIMessage.jsx b/src/components/assistant/DoormileAI/AIMessage.jsx index 3d31fff..a619585 100644 --- a/src/components/assistant/DoormileAI/AIMessage.jsx +++ b/src/components/assistant/DoormileAI/AIMessage.jsx @@ -6,8 +6,7 @@ import { LuChevronDown } from 'react-icons/lu'; import { HStack } from '@astryxdesign/core/HStack'; import { VStack } from '@astryxdesign/core/VStack'; import { Text } from '@astryxdesign/core/Text'; -import { IconButton } from '@astryxdesign/core/IconButton'; -import { Button } from '@astryxdesign/core/Button'; +import { Button } from '@/components/ui/button'; import { ChatToolCalls } from '@astryxdesign/core/Chat'; import { Spark, Metric, StatGrid, StateBlock, AnswerList } from './AIParts'; @@ -138,35 +137,46 @@ const AssistantMessage = ({ message, onCopy, onSubmitForm, onCancelAction, onCho {['confirmCustomer', 'confirmOrder', 'confirmBulk', 'confirmAssign'].includes(message.form?.kind) && ( {message.form.status === 'open' ? ( - +
+ + + {/* A second, wider commit — used when rows were excluded by default but including them is a legitimate choice. */} {message.form.altDraft && ( )} - +
) : ( {message.form.status === 'done' diff --git a/src/components/assistant/DoormileAI/AIRowsStep.jsx b/src/components/assistant/DoormileAI/AIRowsStep.jsx index 2f8fa14..c02916d 100644 --- a/src/components/assistant/DoormileAI/AIRowsStep.jsx +++ b/src/components/assistant/DoormileAI/AIRowsStep.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { VStack } from '@astryxdesign/core/VStack'; import { HStack } from '@astryxdesign/core/HStack'; import { Text } from '@astryxdesign/core/Text'; -import { Button } from '@astryxdesign/core/Button'; +import { Button } from '@/components/ui/button'; import { TextArea } from '@astryxdesign/core/TextArea'; import { FileInput } from '@astryxdesign/core/FileInput'; @@ -23,41 +23,38 @@ import { parseBulkFile, templateCsv, downloadCsv } from '@/lib/assistant/bulkFil // // What is reported before the operator can continue, and why each matters: // • rows read — so a truncated or half-empty sheet is visible immediately -// • columns NOT recognised, by name — an unread price column would otherwise -// silently re-quote every row -// • rows repeated inside the file — the bulk endpoint has no idempotency key, -// so a duplicate that gets through is a second real rider at one door - -const PLACEHOLDER = `Ramesh Kumar, 9876543210, 12 Cross Cut Rd Gandhipuram, 641012, Coimbatore, 120, 2 meal boxes -Priya N, 9876500011, 45 RS Puram, 641002, Coimbatore, 95, documents`; - +// • unusable rows — bad phones, empty lines, missing fields; surfaced +// up-front so the operator can fix the sheet instead of creating 9 good +// orders and wondering where the 10th went +// • columns ignored — confirms the resolver didn't mistake an unknown +// column for a standard one const AIRowsStep = ({ onSubmit, onCancel, isBusy }) => { - const [file, setFile] = useState(null); - const [fileParse, setFileParse] = useState(null); - const [fileError, setFileError] = useState(null); - const [raw, setRaw] = useState(''); + const [text, setText] = useState(''); const [isReading, setIsReading] = useState(false); + const [parsed, setParsed] = useState(null); - const pasted = raw.trim() ? parseBulkRows(raw) : null; - // A loaded file wins; clearing it falls back to whatever is in the textarea. - const parsed = fileParse || pasted; - - const handleFile = async (picked) => { - const chosen = Array.isArray(picked) ? picked[0] : picked; - setFile(chosen || null); - setFileError(null); - if (!chosen) { - setFileParse(null); + const handlePasteChange = (val) => { + setText(val); + if (!val.trim()) { + setParsed(null); return; } + setParsed(parseBulkRows(val)); + }; + + const handleFilePicked = async (files) => { + const file = Array.isArray(files) ? files[0] : files; + if (!file) return; setIsReading(true); try { - const result = await parseBulkFile(chosen); - setFileParse(result); - setRaw(''); - } catch (err) { - setFileParse(null); - setFileError(err.message || 'Couldn’t read that file.'); + const res = await parseBulkFile(file); + setParsed(res); + } catch { + setParsed({ + rows: [], + errors: [{ row: 0, reason: 'Failed to read file' }], + ignoredColumns: [] + }); } finally { setIsReading(false); } @@ -66,51 +63,46 @@ const AIRowsStep = ({ onSubmit, onCancel, isBusy }) => { return ( - {!fileParse && ( -