updates on the api integration and the design changes on the whole website

This commit is contained in:
2026-08-11 11:18:40 +05:30
parent d144ff71a9
commit bf28249528
48 changed files with 2888 additions and 6361 deletions

View File

@@ -1,5 +1,6 @@
import axios from 'axios';
import { OpenToast } from 'components/third-party/OpenToast';
import logger from 'utils/logger';
import {
getMilers,
getMiler,
@@ -14,13 +15,10 @@ import {
getBooking,
assignMilerToBooking,
cancelBooking,
bulkCancelBookings,
updateConsignmentStatus,
getAdminPricing,
getPartners,
getReports,
getLocationsSummary,
getDashboard,
getProfile
} from 'pages/api/doormileApi';
@@ -88,8 +86,10 @@ export const getRiderPeriodicLogs = async (userid) => {
const logs = await getMilerLogs(userid, undefined, undefined, 1);
const latest = Array.isArray(logs) ? logs[0] : logs;
if (latest) return latest;
} catch (err) {
console.log('getRiderPeriodicLogs', err.message);
} catch {
// Falls through to the getMiler() fallback below — this is polled every
// 15s by the live map, so a toast here would spam the operator on every
// failed tick. Only surface an error if the fallback also fails.
}
try {
return (await getMiler(userid)) || null;
@@ -122,25 +122,35 @@ export const fetchAppLocations = async () => {
// the doc only promises "counts + today's numbers", not an exact schema.
export const fetchPercentageData = async ({ queryKey }) => {
const [, , startdate, enddate] = queryKey;
const details = (await getReports(startdate, enddate)) || {};
const total = details.total || 0;
return {
created: String(details.created || 0),
uncoveredOrders: String(details.pending || 0),
coveredOrders: String(details.delivered || 0),
cancelled: String(details.cancelled || 0),
percentage1: String(Math.round(((details.created || 0) / total) * 100) || 0),
percentage2: String(Math.round(((details.pending || 0) / total) * 100) || 0),
percentage3: String(Math.round(((details.delivered || 0) / total) * 100) || 0),
percentage4: String(Math.round(((details.cancelled || 0) / total) * 100) || 0)
};
try {
const details = (await getReports(startdate, enddate)) || {};
const total = details.total || 0;
return {
created: String(details.created || 0),
uncoveredOrders: String(details.pending || 0),
coveredOrders: String(details.delivered || 0),
cancelled: String(details.cancelled || 0),
percentage1: String(Math.round(((details.created || 0) / total) * 100) || 0),
percentage2: String(Math.round(((details.pending || 0) / total) * 100) || 0),
percentage3: String(Math.round(((details.delivered || 0) / total) * 100) || 0),
percentage4: String(Math.round(((details.cancelled || 0) / total) * 100) || 0)
};
} catch (err) {
OpenToast(err.response?.data?.message || err.message || 'Failed to load order percentages', 'error', 2000);
return { created: '0', uncoveredOrders: '0', coveredOrders: '0', cancelled: '0', percentage1: '0', percentage2: '0', percentage3: '0', percentage4: '0' };
}
};
// ===================================================== || getTenants || =====================================================
export const getTenants = async () => {
const tenants = await getAdminTenants();
return (tenants || []).map((val) => ({ ...val, label: val.tenantname }));
try {
const tenants = await getAdminTenants();
return (tenants || []).map((val) => ({ ...val, label: val.tenantname }));
} catch (err) {
OpenToast(err.response?.data?.message || err.message || 'Failed to load tenants', 'error', 2000);
return [];
}
};
// ============================================= || gettenantlocations (branches) || =============================================
@@ -148,31 +158,14 @@ export const gettenantlocations = async (tenantId) => {
try {
return (await getTenantLocations(tenantId)) || [];
} catch (err) {
console.log('gettenantlocations', err);
// Log only the message — the raw error object carries the request
// config, including the Authorization: Bearer <doormileToken> header
// doormileAxios attaches, which shouldn't hit the console.
OpenToast(err.response?.data?.message || err.message || 'Failed to load tenant locations', 'error', 2000);
return [];
}
};
// ==============================|| fetchorderscount (orders) ||============================== //
export const fetchorderscount = async ({ queryKey }) => {
const [, , startdate, enddate] = queryKey;
return (await getReports(startdate, enddate)) || {};
};
// ==============================|| fetchOrders (orders) ||============================== //
// Maps to /admin/bookings. The old endpoint's applocationid/tenantid/locationid
// filters and free-text `status` are not documented on this resource — pageno/
// pagesize are (see "Conventions" in express-console-api.md).
export const fetchOrders = async ({ pageParam = 1, queryKey }) => {
const [, , , , , , rowsPerPage] = queryKey;
const rows = (await getBookings(pageParam, rowsPerPage)) || [];
return {
rows,
nextPage: rows.length === Number(rowsPerPage) ? pageParam + 1 : undefined
};
};
// ==============================|| fetchPaymentType (orders) ||============================== //
// No payment-mode-types endpoint in the new API.
export const fetchPaymentType = async () => [];
@@ -187,8 +180,14 @@ export const fetchRidersList = async () => {
label: `${val.displayname || val.authname || ''} | ${val.contactno || ''}`
}));
} catch (err) {
OpenToast(err.message, 'error', 2000);
throw err;
// Was `throw`ing after already toasting here — deliveries.js also wires
// its own onError toast on this same query, so a real failure showed
// the operator the same error twice. Matches every sibling function's
// convention now: toast once, return a safe default, never throw (the
// other 4 call sites had no onError of their own and got zero feedback
// on failure before this fix).
OpenToast(err.response?.data?.message || err.message || 'Failed to load riders', 'error', 2000);
return [];
}
};
@@ -217,21 +216,128 @@ export const fetchBatchEfficiency = async ({ batch, tenantId }) => {
return response.data;
};
// ==============================|| finalCreatedeliveries (orders) ||============================== //
// Final delivery commit — also part of the separate solver/optimiser pipeline,
// left untouched (still posts to jupiter.nearle.app directly, not via
// REACT_APP_URL, so this migration doesn't need to change it).
// Final delivery commit. Used to POST the whole batch to jupiter's
// /deliveries/createdeliveries in one shot — retired for two confirmed
// reasons: (1) it 500'd because jupiter's Deliveries Go struct types
// pickuplat/pickuplon/droplat/droplon as string, while GET /admin/bookings
// (the new Doormile API) feeds orders.js's solver payload with those as
// numbers; (2) even once that's patched, jupiter writes into jupiter's own
// now-orphaned database — neither the Orders "pending" list nor the
// Deliveries "dispatched" filter read it, since both come from Doormile's
// GET /admin/bookings. jupiter2doormile.md §4 (the "assign a rider" row,
// mapped from jupiter's old PUT /deliveries/updatedelivery) and
// express-console-api.md's exercised-routes list both confirm
// POST /admin/bookings/:id/assign-miler as the correct, live-proven
// replacement for this exact action — not POST /admin/expressbooking
// (that one creates a brand-new booking from scratch; wrong here, since
// every order already exists as a Doormile booking pulled from
// GET /admin/bookings).
export const finalCreatedeliveries = async (deliveryData) => {
const toInt = (v) => {
const n = Number(v);
return Number.isFinite(n) ? n : v;
};
const deliveries = (deliveryData.deliveries || []).map((d) => ({
...d,
userid: toInt(d.userid),
rider_id: toInt(d.rider_id)
}));
const response = await axios.post(`https://jupiter.nearle.app/live/api/v1/deliveries/createdeliveries`, deliveries);
return response.data;
const deliveries = deliveryData.deliveries || [];
logger.debug(`finalCreatedeliveries: ${deliveries.length} order(s) to assign`);
if (!deliveries.length) return { success: true, assigned: 0, failed: 0 };
// Diagnostic only (no PII beyond a rider's display name, which every
// operator already sees in the UI). Shows exactly which candidate field
// names are actually present, so a wrong guess in the fallback chain
// below is visible instead of silently mismatching.
deliveries.forEach((d, i) => {
logger.debug(` order[${i}] id fields:`, {
bookingid: d.bookingid,
orderheaderid: d.orderheaderid,
deliveryid: d.deliveryid,
orderid: d.orderid,
rider_id: d.rider_id,
userid: d.userid,
rider_name: d.rider_name ?? d.rider
});
});
// assign-miler needs a milerprofileid. userid/milerprofileid matching was
// tried first (the solver's rider pool is fed full miler objects — see
// Preview.js's handleCreateDelivery -> `riders: autoRiders`, raw
// GET /admin/milers data carrying both fields per rider) but confirmed
// live that NEITHER matches: the solver's rider_id/userid (e.g. "883")
// isn't in GET /admin/milers under either field. This matches
// Dispatch.js's own Analysis-panel comment — "the workolik solver doesn't
// have our auth/users table" — so its numeric rider ids are its own
// internal numbering, disconnected from Doormile's id space entirely.
// Falls back to matching the rider's NAME (also echoed by the solver,
// see flattenRiders' rider_name) against each miler's displayname/
// authname — the only other correlatable field. Logs the full roster
// alongside so a further mismatch is fully diagnosable from one run.
let milers = [];
try {
milers = (await getMilers()) || [];
} catch (err) {
logger.error('finalCreatedeliveries: GET /admin/milers failed', err.response?.status, err.response?.data || err.message);
}
logger.debug(
`finalCreatedeliveries: ${milers.length} miler(s) available for rider resolution`,
milers.map((m) => ({ userid: m.userid, milerprofileid: m.milerprofileid, name: m.displayname || m.authname }))
);
const milerByUserId = new Map(milers.map((m) => [String(m.userid), m]));
const milerByProfileId = new Map(milers.map((m) => [String(m.milerprofileid), m]));
const normName = (s) => String(s || '').trim().toLowerCase();
const milerByName = new Map();
milers.forEach((m) => {
[m.displayname, m.authname].forEach((n) => {
const key = normName(n);
if (key && !milerByName.has(key)) milerByName.set(key, m);
});
});
const results = await Promise.allSettled(
deliveries.map(async (d) => {
// Booking id: the solver is a separate, unverified service (see this
// area's CLAUDE.md) — falls back through every plausible field name
// an order might carry it under, `bookingid` first since that's what
// orders.js actually sends in and most solvers pass unknown fields
// through untouched.
const bookingId = d.bookingid ?? d.orderheaderid ?? d.deliveryid ?? d.orderid;
const riderUserId = d.rider_id ?? d.userid;
const riderName = d.rider_name ?? d.rider;
const matchedVia = milerByUserId.has(String(riderUserId))
? 'userid'
: milerByProfileId.has(String(riderUserId))
? 'milerprofileid'
: milerByName.has(normName(riderName))
? 'name'
: null;
const rider =
milerByUserId.get(String(riderUserId)) ?? milerByProfileId.get(String(riderUserId)) ?? milerByName.get(normName(riderName));
if (bookingId == null || !rider?.milerprofileid) {
const reason =
bookingId == null
? 'no booking id resolved on the order object'
: `no miler found for rider id ${riderUserId} / name "${riderName}" (checked userid, milerprofileid, name)`;
logger.error(`finalCreatedeliveries: skipping order — ${reason}`);
throw new Error(`order ${d.orderid ?? bookingId ?? '?'}: ${reason}`);
}
logger.debug(`finalCreatedeliveries: order booking ${bookingId} -> rider ${riderUserId} ("${riderName}") matched via ${matchedVia}`);
try {
return await assignMilerToBooking(bookingId, { milerid: rider.milerprofileid });
} catch (err) {
logger.error(
`finalCreatedeliveries: assign-miler failed for booking ${bookingId}`,
err.response?.status,
err.response?.data || err.message
);
throw err;
}
})
);
const failed = results.filter((r) => r.status === 'rejected');
if (failed.length === deliveries.length) {
// Total failure — throw so the caller's mutation onError fires instead
// of a false "success" toast + navigate-away with nothing assigned.
throw new Error(`Couldn't assign any of the ${deliveries.length} order(s)`);
}
if (failed.length) {
OpenToast(`${failed.length} of ${deliveries.length} order(s) couldn't be assigned — check Orders/Deliveries`, 'warning', 4000);
}
return { success: true, assigned: deliveries.length - failed.length, failed: failed.length };
};
// ==============================|| createAutomationDeliveries (orders) Auto rider Assign ||============================== //
// Also part of the optimiser pipeline (routes.workolik.com / routemate.workolik.com) — untouched.
@@ -263,17 +369,6 @@ export const notifyRider = async (
return notifyMiler(milerProfileId, title, message);
};
// ==============================|| cancelOrder (orders) ||============================== //
export const cancelOrder = async (bookingId) => cancelBooking(bookingId);
// ==============================|| cancelMultipleOrder (orders) ||============================== //
export const cancelMultipleOrder = async (orderlist) => {
const bookingIds = (orderlist || []).map((e) => e.bookingid ?? e.id);
return bulkCancelBookings(bookingIds);
};
// ==============================|| fetchDeliveries (deliveries) ||============================== //
// GET /admin/consignments has zero documented response schema (see
// express-console-api.md — it's listed with no example body at all, unlike
@@ -297,8 +392,19 @@ export const cancelMultipleOrder = async (orderlist) => {
// approach as fetchAllRiders' `{details}` wrapper.
export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => {
const [, , , , , , rowsPerPage] = queryKey;
const [bookings, customers, milers, tenants] = await Promise.all([
getBookings(pageParam, rowsPerPage),
// Unlike the 3 joins below (customers/milers/tenants — each individually
// guarded so a failed join just degrades a display field, not the whole
// page), a failed bookings call is the one thing this function can't
// recover from — the previous version let it reject the whole Promise.all
// uncaught, with no toast anywhere across the 4 pages that call this.
let bookings;
try {
bookings = await getBookings(pageParam, rowsPerPage);
} catch (err) {
OpenToast(err.response?.data?.message || err.message || 'Failed to load deliveries', 'error', 2000);
return { rows: [], nextPage: undefined };
}
const [customers, milers, tenants] = await Promise.all([
getAdminCustomers().catch(() => []),
getMilers().catch(() => []),
getAdminTenants().catch(() => [])
@@ -319,6 +425,7 @@ export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => {
deliveryid: b.bookingid,
orderid: b.bookingno || `#${b.bookingid}`,
consignmentid: b.consignmentid,
tenantid: b.tenantid,
tenantname: tenant?.tenantname || '',
tenantsuburb: '',
applocation: '',
@@ -383,38 +490,24 @@ export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => {
};
};
// Shared by fetchPercentageAPI/fetchCountAPI so the chip counts are derived
// from the exact same source (and status mapping) as fetchDeliveries' rows —
// counting against the separately-guessed /admin/dashboard shape produced
// numbers that didn't match what the table actually showed.
// Backs fetchCountAPI so the chip counts are derived from the exact same
// source (and status mapping) as fetchDeliveries' rows — counting against
// the separately-guessed /admin/dashboard shape produced numbers that didn't
// match what the table actually showed.
const getDeliveryStatusCounts = async () => {
const bookings = (await getBookings(1, 1000)) || [];
const dispatched = bookings.filter((b) => b.assignedmileruserid || b.consignmentid);
const counts = { total: dispatched.length };
dispatched.forEach((b) => {
const status = mapBookingStatusToDeliveryStatus(b.status);
counts[status] = (counts[status] || 0) + 1;
});
return counts;
};
// ==============================|| fetchPercentageAPI (deliveries) ||============================== //
export const fetchPercentageAPI = async () => {
const data = await getDeliveryStatusCounts();
return {
coveredOrders: String(data.delivered || 0),
cancelledOrders: String(data.cancelled || 0),
uncoveredOrders: String(data.pending || 0),
assignedOrders: String(data.accepted || 0),
createdOrders: String(data.total || 0),
closedOrders: String(data.delivered || 0),
pickedOrders: String(data.picked || 0),
percentage1: String(Math.round(((data.pending || 0) / (data.total || 1)) * 100) || 0),
percentage2: String(Math.round(((data.accepted || 0) / (data.total || 1)) * 100) || 0),
percentage3: String(Math.round(((data.picked || 0) / (data.total || 1)) * 100) || 0),
percentage4: String(Math.round(((data.delivered || 0) / (data.total || 1)) * 100) || 0)
};
try {
const bookings = (await getBookings(1, 1000)) || [];
const dispatched = bookings.filter((b) => b.assignedmileruserid || b.consignmentid);
const counts = { total: dispatched.length };
dispatched.forEach((b) => {
const status = mapBookingStatusToDeliveryStatus(b.status);
counts[status] = (counts[status] || 0) + 1;
});
return counts;
} catch (err) {
OpenToast(err.response?.data?.message || err.message || 'Failed to load delivery counts', 'error', 2000);
return {};
}
};
// ==============================|| fetchCountAPI (deliveries) ||============================== //
@@ -473,21 +566,6 @@ export const getalltenants = async () => {
return null;
}
};
// ==============================|| gettenantsummary (tenants) ||============================== //
// No tenant-tab-counts endpoint in the new API.
export const gettenantsummary = async () => null;
// ==============================|| getpricinglist (tenants) ||============================== //
export const getpricinglist = async () => {
try {
return (await getAdminPricing()) || [];
} catch (err) {
const message = err.response?.data?.message || err.message || 'Something went wrong';
OpenToast(message);
return null;
}
};
// ==============================|| getallpricing (clientPricing) ||============================== //
// This is the client-facing "Pricing" page (tenant pricing rules, GET
// /admin/pricing) — not GET /admin/doormile-pricing, which is Doormile's own
@@ -510,7 +588,7 @@ export const fetchAllRiders = async () => {
const milers = await getMilers();
return { details: milers || [], nextPage: undefined };
} catch (err) {
console.log('fetchAllRiders err', err.message);
OpenToast(err.response?.data?.message || err.message || 'Failed to load riders', 'error', 2000);
return { details: [], nextPage: undefined };
}
};
@@ -519,36 +597,31 @@ export const getallridersummary = async () => {
try {
const milers = (await getMilers()) || [];
// A miler has no `status` field — availabilitystatus is the real one
// (confirmed live: Available/Assigned/On_Pickup/Offline). "Active" here
// means not Offline, matching riders.js's own tab filter.
const active = milers.filter((m) => String(m.availabilitystatus || '').toLowerCase() !== 'offline').length;
// (confirmed live: Available/Assigned/On_Pickup/Offline/Blocked).
// "Active" here means not Offline and not Blocked, matching riders.js's
// own tab filter.
const active = milers.filter((m) => !['offline', 'blocked'].includes(String(m.availabilitystatus || '').toLowerCase())).length;
return { total: milers.length, active, inactive: milers.length - active };
} catch (err) {
console.log('getallridersummary err', err.message);
OpenToast(err.response?.data?.message || err.message || 'Failed to load rider summary', 'error', 2000);
return { total: 0, active: 0, inactive: 0 };
}
};
// ==============================|| fetchRiders (riders), active riders ||============================== //
export const fetchRiders = async () => {
try {
const milers = await getMilers();
return { details: milers || [], nextPage: undefined };
} catch (err) {
console.log('fetchRiders err', err.message);
return [];
}
};
// ==============================|| getriderstatus (riders)||============================== //
// No check-in-history endpoint in the new API.
export const getriderstatus = async () => null;
// ==============================|| getreportsummary (orders summary)||============================== //
// appId (queryKey[0]) was previously discarded here — the LocationAutocomplete
// zone picker on the Orders Summary page changed the header text but never
// the actual request. GET /admin/reports does accept a hubid param (see
// getReports in doormileApi.js) — hubs are this API's zone-equivalent (see
// LocationAutocomplete's own comment) — so pass appId through as that.
export const getreportsummary = async ({ queryKey }) => {
const [, tenantid, locationid, startdate, enddate] = queryKey;
return (await getReports(startdate, enddate, tenantid, locationid)) || {};
const [appId, tenantid, locationid, startdate, enddate] = queryKey;
try {
return (await getReports(startdate, enddate, tenantid, locationid, appId || undefined)) || {};
} catch (err) {
OpenToast(err.response?.data?.message || err.message || 'Failed to load the report summary', 'error', 2000);
return {};
}
};
// ==============================|| getreportlocationsummary (orders summary)||============================== //
@@ -556,24 +629,14 @@ export const getreportsummary = async ({ queryKey }) => {
// jupiter's getlocationsummary — GET /admin/locations/summary.
export const getreportlocationsummary = async ({ queryKey }) => {
const [, tenantid, locationid, startdate, enddate] = queryKey;
return (await getLocationsSummary(tenantid, locationid, startdate, enddate)) || [];
try {
return (await getLocationsSummary(tenantid, locationid, startdate, enddate)) || [];
} catch (err) {
OpenToast(err.response?.data?.message || err.message || 'Failed to load the location summary', 'error', 2000);
return [];
}
};
// ==============================|| fetchorderdetails (orders detail)||============================== //
export const fetchorderdetails = async ({ queryKey }) => {
const [, , , page, rowsPerPage] = queryKey;
const rows = (await getBookings(page, rowsPerPage)) || [];
return rows.map((item, index) => ({ ...item, sno: index + 1 }));
};
// ==============================|| getriderbydelivery (orders detail)||============================== //
// No per-rider-delivery-trace endpoint in the new API.
export const getriderbydelivery = async () => [];
// ==============================|| fetchCount (orders detail)||============================== //
export const fetchCount = async () => (await getDashboard()) || {};
// ==============================|| fetchRidersSummary (riders summary)||============================== //
// No per-rider performance summary endpoint in the new API.
// Confirmed against jupiter2doormile.md (Status: Done) as the replacement
@@ -582,14 +645,13 @@ export const fetchCount = async () => (await getDashboard()) || {};
// as-is and lets the page's own rendering degrade gracefully on unknown keys.
export const fetchRidersSummary = async ({ queryKey }) => {
const [, appId, startdate, enddate] = queryKey;
const data = await getMilerSummary(appId || undefined, startdate, enddate);
return Array.isArray(data) ? data : data ? [data] : [];
};
// ==============================|| fetchLocations (orders summary))||============================== //
export const fetchLocations = async () => {
const partners = (await getPartners()) || [];
return [...partners, { partnername: 'All', partnerid: -1 }];
try {
const data = await getMilerSummary(appId || undefined, startdate, enddate);
return Array.isArray(data) ? data : data ? [data] : [];
} catch (err) {
OpenToast(err.response?.data?.message || err.message || 'Failed to load the riders summary', 'error', 2000);
return [];
}
};
// ==============================|| fetchRidersLogs (RiderLogs)||============================== //
@@ -627,26 +689,21 @@ export const fetchRidersLogs = async ({ queryKey } = {}) => {
latitude: r.currentlatitude,
longitude: r.currentlongitude
}));
} catch (err) {
console.log('fetchRidersLogs', err.message);
} catch {
// Polled every 1s by Dispatch.js's live map (RIDER_LOG_POLL_MS) — a toast
// here would fire once a second on any outage. Fail silently; the map
// just stops updating rider pins until the next successful poll.
return [];
}
};
// ==============================|| getorders (Locations)||============================== //
export const fetchOrders1 = async ({ pageParam = 1, queryKey }) => {
const [, , , , , , , rowsPerPage] = queryKey;
const rows = (await getBookings(pageParam, rowsPerPage)) || [];
return { details: rows, nextPage: rows.length === rowsPerPage ? pageParam + 1 : undefined };
};
// ==============================|| getusers (viewProfile)||============================== //
export const getusers = async () => {
try {
return (await getProfile()) || {};
} catch (err) {
console.log('getusers', err.message);
OpenToast(err.response?.data?.message || err.message || 'Failed to load profile', 'error', 2000);
return {};
}
};
@@ -656,7 +713,7 @@ export const getallriders = async () => {
try {
return (await getMilers()) || [];
} catch (err) {
console.log('getallriders', err.message);
OpenToast(err.response?.data?.message || err.message || 'Failed to load riders', 'error', 2000);
return [];
}
};