updates on the ui changes and changed into doormile

This commit is contained in:
2026-08-24 18:10:47 +05:30
parent dcf9770ada
commit aa42d9ee81
409 changed files with 49950 additions and 66532 deletions

View File

@@ -0,0 +1,413 @@
import {
Clock3 as LuClock3,
Package as LuPackage,
Bike as LuBike,
Truck as LuTruck,
Building2 as LuBuilding2,
CircleDot as LuCircleDot,
Banknote as LuBanknote,
Users as LuUsers,
Layers as LuLayers,
TimerOff as LuTimerOff,
UserPlus as LuUserPlus,
PackagePlus as LuPackagePlus,
ListPlus as LuListPlus,
Repeat as LuRepeat
} from 'lucide-react';
// ==============================|| Doormile AI — page context ||============================== //
//
// Maps the current route to (a) a short context label shown under the panel
// header and (b) the suggested questions offered in the welcome state, so the
// assistant opens on questions relevant to what the operator is looking at
// rather than a fixed Orders-shaped list.
//
// Every suggestion here MUST be a phrasing the deterministic matcher in
// intents.js actually resolves — a suggestion chip that returns "I can't
// answer that yet" is worse than no chip. When adding one, check it against
// the INTENTS catalog first.
// ---------------------------------------------------------------------------
// Short chip labels.
//
// Every `text` below is the exact phrasing the deterministic matcher in
// intents.js resolves, so it CANNOT be shortened in place — that is the string
// the click actually asks. This map only changes what the chip DISPLAYS.
//
// It exists because the welcome state offers up to twenty suggestions, and as
// full sentences they are 130-255px wide in a ~393px panel: barely two fit per
// row, so the "chips" laid out as fourteen near-full-width rows and read as a
// list again. Shortened, three or four fit per row and the block becomes
// something you scan.
//
// The full question stays the accessible name and the tooltip, so nothing is
// lost for screen readers or for anyone unsure what a chip will ask. A
// suggestion with no entry here simply shows its full text.
// ---------------------------------------------------------------------------
export const CHIP_LABELS = {
"Give me today's operations summary": "Today's summary",
'How many orders today?': 'Orders today',
'Which orders are delayed?': 'Delayed orders',
'Create an order': 'Create order',
'Create multiple orders': 'Bulk orders',
'Total revenue today': 'Revenue today',
'How many pending orders today?': 'Pending today',
'How many cancelled orders today?': 'Cancelled today',
'How many delivered orders today?': 'Delivered today',
'Morning Batch orders today': 'Morning batch',
'Orders today vs yesterday': 'Today vs yesterday',
'Create a customer': 'Create customer',
'How many riders are active?': 'Active riders',
'How many vehicles are available?': 'Vehicles available',
'Current hub status': 'Hub status',
'Which hubs are experiencing delays?': 'Hub delays',
'How many tenants do we have?': 'Tenants',
'How many orders this week?': 'Orders this week',
'Total revenue this week': 'Revenue this week',
'How many customers do we have?': 'Customers',
'Assign a rider': 'Assign order',
"Repeat yesterday's orders": 'Repeat yesterday'
};
// The one follow-up phrasing that is not already a page suggestion. Verified
// against ASSIGN_TRIGGER in assignActions.js — it opens the assign-rider flow.
const ASSIGN_TEXT = 'Assign a rider';
// Recreates a previous day's run rather than building an order from scratch.
// This is the phrasing intents.js documents for it ("repeat yesterday's
// orders") and it is matched by REPEAT_TRIGGER in repeatRuns.js, which is
// ordered AHEAD of createOrder/createBulkOrders precisely so a sentence
// containing both "repeat" and "orders" recalls the run instead of opening a
// blank create form.
const REPEAT_TEXT = "Repeat yesterday's orders";
// Not a question — an OUTCOME. AIPanel sets this as the follow-up key when a
// create or repeat run actually succeeds, so the next steps offered are the
// ones that follow from an order existing, rather than the ones that follow
// from having asked to make one. Assigning a rider only makes sense on this
// 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: [
{ icon: LuClock3, text: "Give me today's operations summary" },
{ icon: LuPackage, text: 'How many orders today?' },
{ icon: LuTimerOff, text: 'Which orders are delayed?' },
{ icon: LuPackagePlus, text: 'Create an order' },
{ icon: LuRepeat, text: REPEAT_TEXT }
],
more: [
{ icon: LuListPlus, text: 'Create multiple orders' },
{ icon: LuBanknote, text: 'Total revenue today' },
{ icon: LuCircleDot, text: 'How many pending orders today?' },
{ icon: LuPackage, text: 'How many cancelled orders today?' },
{ icon: LuPackage, text: 'How many delivered orders today?' },
{ icon: LuLayers, text: 'Morning Batch orders today' },
{ icon: LuClock3, text: 'Orders today vs yesterday' }
]
};
const RIDERS = {
label: 'Riders',
suggestions: [
{ icon: LuBike, text: 'How many riders are active?' },
{ icon: LuClock3, text: "Give me today's operations summary" },
{ icon: LuPackage, text: 'How many orders today?' },
{ icon: LuTruck, text: 'How many vehicles are available?' }
],
more: [
{ icon: LuBuilding2, text: 'Current hub status' },
{ icon: LuBanknote, text: 'Total revenue today' }
]
};
const VEHICLES = {
label: 'Vehicles',
suggestions: [
{ icon: LuTruck, text: 'How many vehicles are available?' },
{ icon: LuBike, text: 'How many riders are active?' },
{ icon: LuBuilding2, text: 'Current hub status' },
{ icon: LuClock3, text: "Give me today's operations summary" }
],
more: [{ icon: LuPackage, text: 'How many orders today?' }]
};
const HUBS = {
label: 'Hubs',
suggestions: [
{ icon: LuBuilding2, text: 'Current hub status' },
{ icon: LuTimerOff, text: 'Which hubs are experiencing delays?' },
{ icon: LuTruck, text: 'How many vehicles are available?' },
{ icon: LuBike, text: 'How many riders are active?' }
],
more: [{ icon: LuPackage, text: 'How many orders today?' }]
};
const DISPATCH = {
label: 'Dispatch',
suggestions: [
{ icon: LuLayers, text: 'Morning Batch orders today' },
{ icon: LuTimerOff, text: 'Which orders are delayed?' },
{ icon: LuBike, text: 'How many riders are active?' },
{ icon: LuClock3, text: "Give me today's operations summary" }
],
more: [
{ icon: LuPackage, text: 'How many orders today?' },
{ icon: LuCircleDot, text: 'How many pending orders today?' }
]
};
const TENANTS = {
label: 'Tenants',
suggestions: [
{ icon: LuUsers, text: 'How many tenants do we have?' },
{ icon: LuPackage, text: 'How many orders today?' },
{ icon: LuBanknote, text: 'Total revenue today' },
{ icon: LuClock3, text: "Give me today's operations summary" }
],
more: []
};
const REPORTS = {
label: 'Reports',
suggestions: [
{ icon: LuBanknote, text: 'Total revenue today' },
{ icon: LuClock3, text: 'Orders today vs yesterday' },
{ icon: LuPackage, text: 'How many orders this week?' },
{ icon: LuClock3, text: "Give me today's operations summary" }
],
more: [{ icon: LuBanknote, text: 'Total revenue this week' }]
};
const DEFAULT_CONTEXT = {
label: 'Operations',
suggestions: [
{ icon: LuClock3, text: "Give me today's operations summary" },
{ icon: LuPackage, text: 'How many orders today?' },
{ icon: LuBike, text: 'How many riders are active?' },
{ icon: LuBuilding2, text: 'Current hub status' }
],
more: [
{ icon: LuBanknote, text: 'Total revenue today' },
{ icon: LuTruck, text: 'How many vehicles are available?' }
]
};
// ---------------------------------------------------------------------------
// Follow-ups.
//
// What the suggestion strip offers AFTER a question has been asked. Showing
// all twenty again is noise — the operator has already told you what they are
// doing, so the next set should be the natural next steps for it. Creating an
// order leads to assigning a rider and to what is still pending; asking about
// revenue leads to the week and to the day-over-day comparison.
//
// Every string here is either one of the CHIP_LABELS questions above (all of
// which the matcher already resolves) or ASSIGN_TEXT. That constraint is the
// whole point: pageContext's rule is that a suggestion which returns "I can't
// answer that yet" is worse than no suggestion, and a follow-up is MORE likely
// to be clicked than a cold one, because it arrives exactly when it is
// relevant. Do not add a phrasing here without checking it against INTENTS.
// ---------------------------------------------------------------------------
const FOLLOW_UPS = {
// --- write actions ---
// Note what is NOT here: assigning a rider.
//
// "Create an order" only STARTS the create conversation — no order exists at
// that moment, so offering to assign one is offering to act on nothing. And
// once the order really is created, AIPanel already drops the operator
// straight into the assign flow itself (see the confirmOrder branch), because
// that hand-off is mandatory rather than optional. A chip there would either
// duplicate a step the product performs on its own or fire it too early.
//
// ASSIGN_TEXT belongs on the questions below that surface orders which
// already EXIST and are waiting for a rider — and on ORDER_CREATED, which is
// keyed off the write succeeding rather than off anything that was asked.
'Create an order': [REPEAT_TEXT, 'How many pending orders today?', 'Which orders are delayed?', 'Create multiple orders'],
'Create multiple orders': ['How many pending orders today?', 'How many orders today?'],
[REPEAT_TEXT]: ['How many pending orders today?', 'How many orders today?', 'Orders today vs yesterday'],
'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": [
'How many pending orders today?',
'Which orders are delayed?',
'Total revenue today',
'How many riders are active?'
],
'How many orders today?': [
'How many pending orders today?',
'How many delivered orders today?',
'Orders today vs yesterday',
'Total revenue today'
],
'How many pending orders today?': [ASSIGN_TEXT, 'Which orders are delayed?', 'How many delivered orders today?'],
'How many delivered orders today?': ['How many pending orders today?', 'Total revenue today', 'Orders today vs yesterday'],
'How many cancelled orders today?': ['How many delivered orders today?', 'How many orders today?', 'Which orders are delayed?'],
'Which orders are delayed?': [ASSIGN_TEXT, 'Which hubs are experiencing delays?', 'How many riders are active?'],
'Morning Batch orders today': ['Which orders are delayed?', 'How many riders are active?', 'How many orders today?'],
// --- money and trend ---
'Total revenue today': ['Total revenue this week', 'Orders today vs yesterday', 'How many orders today?'],
'Total revenue this week': ['How many orders this week?', 'Total revenue today'],
'Orders today vs yesterday': [REPEAT_TEXT, 'Total revenue today', 'How many orders this week?'],
'How many orders this week?': ['Total revenue this week', 'How many orders today?'],
// --- fleet and network ---
'How many riders are active?': ['How many vehicles are available?', 'Current hub status', ASSIGN_TEXT],
'How many vehicles are available?': ['How many riders are active?', 'Current hub status'],
'Current hub status': ['Which hubs are experiencing delays?', 'How many vehicles are available?', 'How many riders are active?'],
'Which hubs are experiencing delays?': ['Current hub status', 'Which orders are delayed?', 'How many riders are active?'],
// --- accounts ---
'How many tenants do we have?': ['How many customers do we have?', 'How many orders today?'],
'How many customers do we have?': ['Create a customer', 'How many tenants do we have?']
};
// Longest-prefix first so 'orders/create' doesn't fall through to 'orders'
// with the wrong label.
const ROUTES = [
['/doormile/dispatch', DISPATCH],
['/doormile/deliveries', { ...ORDERS, label: 'Deliveries' }],
['/doormile/orders', ORDERS],
['/doormile/riders', RIDERS],
['/doormile/vehicles', VEHICLES],
['/doormile/hubs', HUBS],
['/doormile/tenants', TENANTS],
['/doormile/reports', REPORTS],
[
'/doormile/customers',
{
label: 'Customers',
suggestions: [
{ icon: LuUserPlus, text: 'Create a customer' },
{ icon: LuUsers, text: 'How many customers do we have?' },
{ icon: LuClock3, text: "Give me today's operations summary" },
{ icon: LuPackage, text: 'How many orders today?' }
],
more: [{ icon: LuUsers, text: 'How many tenants do we have?' }]
}
],
['/doormile/tripsheets', { ...DEFAULT_CONTEXT, label: 'Tripsheets' }],
['/doormile/exceptions', { ...DEFAULT_CONTEXT, label: 'Exceptions' }],
['/doormile/pricing', { ...DEFAULT_CONTEXT, label: 'Pricing' }]
];
// ---- every question, from every page ---------------------------------------
//
// The catalog is the same wherever you are: the assistant can answer about
// orders, riders, hubs and the rest regardless of which screen is open, so
// hiding a question because you happen to be on Dispatch made it look narrower
// than it is. What the page still decides is ORDER — its own questions lead,
// then everything else follows, deduplicated by text.
//
// `more` is retired: AIWelcome renders one flat list now, so a second array
// would only be a second place for a question to hide.
const ALL_CONTEXTS = [ORDERS, RIDERS, VEHICLES, HUBS, DISPATCH, TENANTS, REPORTS, DEFAULT_CONTEXT, ...ROUTES.map(([, c]) => c)];
// Every question declared anywhere. Two jobs: it is the icon index behind
// QUESTION_BY_TEXT (so a follow-up naming a question that is no longer global
// still renders with the right icon), and it is the pool GLOBAL_SUGGESTIONS
// filters. Do not narrow it — narrow GLOBAL_TEXTS instead.
const EVERY_QUESTION = (() => {
const seen = new Set();
const out = [];
ALL_CONTEXTS.forEach((c) => {
[...(c.suggestions || []), ...(c.more || [])].forEach((q) => {
if (seen.has(q.text)) return;
seen.add(q.text);
out.push(q);
});
});
return out;
})();
// ---------------------------------------------------------------------------
// The questions that ride along on EVERY page.
//
// This used to be EVERY_QUESTION — all twenty-one, everywhere — which meant the
// Orders page offered "How many tenants do we have?" and "Current hub status"
// above the composer, questions an operator working orders is not asking. The
// strip is a shortcut, and a shortcut that lists everything is a menu.
//
// What is left is the daily operating picture: what came in, what is stuck,
// what got out, what it earned, and the three ways to put work into the system.
//
// Dropping a question from THIS list does not remove it from the assistant —
// it stays typeable, it stays a follow-up, and it still leads on its own page,
// because withGlobalQuestions puts each page's own suggestions first. Hubs
// still opens on hub questions; they just no longer follow you to Orders.
// ---------------------------------------------------------------------------
const GLOBAL_TEXTS = new Set([
"Give me today's operations summary",
'How many orders today?',
'Which orders are delayed?',
'How many pending orders today?',
'How many delivered orders today?',
'How many cancelled orders today?',
'Morning Batch orders today',
'Orders today vs yesterday',
'Total revenue today',
'How many riders are active?',
'Create an order',
REPEAT_TEXT,
'Create multiple orders'
]);
const GLOBAL_SUGGESTIONS = EVERY_QUESTION.filter((q) => GLOBAL_TEXTS.has(q.text));
// The page's own suggestions lead; the global set follows, deduplicated.
const withGlobalQuestions = (context) => {
const seen = new Set();
const suggestions = [];
[...(context.suggestions || []), ...(context.more || []), ...GLOBAL_SUGGESTIONS].forEach((q) => {
if (seen.has(q.text)) return;
seen.add(q.text);
suggestions.push(q);
});
return { ...context, suggestions, more: [] };
};
export const getPageContext = (pathname = '') => {
const match = ROUTES.find(([prefix]) => pathname.startsWith(prefix));
return withGlobalQuestions(match ? match[1] : DEFAULT_CONTEXT);
};
// ---------------------------------------------------------------------------
// Follow-ups are stored as bare strings (they have to be — they are the exact
// text that gets asked), so this turns one back into a renderable chip by
// looking its icon up among the questions already declared above. Built from
// EVERY_QUESTION rather than a second hand-written list, so a suggestion can
// never end up with an icon here that disagrees with its icon on a page.
// ---------------------------------------------------------------------------
const QUESTION_BY_TEXT = new Map(EVERY_QUESTION.map((q) => [q.text, q]));
QUESTION_BY_TEXT.set(ASSIGN_TEXT, { icon: LuBike, text: ASSIGN_TEXT });
// Returns [] for anything unrecognised — a typed question, or one with no
// natural next step. The caller falls back to the page's own suggestions, so
// the strip is never empty.
export const getFollowUps = (askedText) => {
const next = FOLLOW_UPS[askedText];
if (!next) return [];
return next.map((text) => QUESTION_BY_TEXT.get(text)).filter(Boolean);
};
// Turns arbitrary follow-up strings into chips. Used for the intent-keyed
// FOLLOW_UP_SUGGESTIONS in intents.js, whose phrasings ("What about
// yesterday?", "This week") are conversational rather than one of the declared
// questions, so they have no icon of their own.
export const toChips = (texts = []) => texts.map((text) => QUESTION_BY_TEXT.get(text) || { icon: LuCircleDot, text });