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,655 @@
import doormileAxios, { DOORMILE_TOKEN_KEY, DOORMILE_USER_KEY } from './client';
// The Doormile Express admin API — one named async export per
// `api.doormile.com/api/v1/admin/*` endpoint.
//
// Ported verbatim from the Express Console's `pages/api/doormileApi.js`, since
// this is the contract the backend actually speaks; only the client import
// changed. Every function unwraps the envelope for its caller: list/GET
// functions return `response.data.data` directly, mutations return the full
// `response.data` (`{ success, data | message }`) so callers can check
// `res.success`.
//
// Pages consume these through the React Query hooks in `@/lib/doormileHooks`
// rather than calling them directly, so cache keys stay coherent across the app.
const buildQuery = (params = {}) => {
const usp = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') usp.append(key, value);
});
const qs = usp.toString();
return qs ? `?${qs}` : '';
};
// ==============================|| Auth ||============================== //
export const loginAdmin = async (email, password) => {
const response = await doormileAxios.post('/admin/login', { email, password });
if (response.data.success) {
localStorage.setItem(DOORMILE_TOKEN_KEY, response.data.token);
localStorage.setItem(DOORMILE_USER_KEY, JSON.stringify(response.data.user));
}
return response.data;
};
export const logoutAdmin = () => {
localStorage.removeItem(DOORMILE_TOKEN_KEY);
localStorage.removeItem(DOORMILE_USER_KEY);
};
// Drains a paginated list endpoint instead of taking whatever the server's
// default page size happens to be.
//
// GET /admin/customers defaults to `pagesize: 20` — confirmed live against an
// account with 37 customers, which came back as
// { pageno: 1, pagesize: 20, pages: 2, total: 37 }. Every caller of
// getAdminCustomers was therefore seeing 20 of 37 and had no way to know:
// the Customers page reported "Total Customers 20", and the customer lookups
// on the orders pages silently missed anyone on page 2.
//
// (express-console-api.md claims the default is 500. It is not, at least not
// for this endpoint. Trust the envelope, not the doc.)
const PAGE_SIZE = 100; // the doc's own example value — safer than assuming the 1000 cap holds
const MAX_PAGES = 12;
const fetchAllPages = async (path) => {
const first = await doormileAxios.get(`${path}${buildQuery({ pageno: 1, pagesize: PAGE_SIZE })}`);
const body = first.data || {};
const rows = body.data || [];
const total = Number(body.total);
if (!Number.isFinite(total) || rows.length >= total) return rows;
const pageCount = Math.min(Math.ceil(total / PAGE_SIZE), MAX_PAGES);
for (let page = 2; page <= pageCount; page += 1) {
const next = await doormileAxios.get(`${path}${buildQuery({ pageno: page, pagesize: PAGE_SIZE })}`);
const more = next.data?.data || [];
if (!more.length) break;
rows.push(...more);
}
return rows;
};
// ==============================|| Dashboard, profile, reports ||============================== //
export const getDashboard = async () => {
const response = await doormileAxios.get('/admin/dashboard');
return response.data.data;
};
// jupiter used fromdate/todate + applocationid (city); Doormile uses from/to
// and no city filter at all (filters by hubid instead — see
// jupiter2doormile.md §2.3/§6). Response carries by_location/by_hub/by_tenant/
// by_rider blocks per that doc, not documented in express-console-api.md.
export const getReports = async (from, to, tenantid, locationid, hubid) => {
const response = await doormileAxios.get(`/admin/reports${buildQuery({ from, to, tenantid, locationid, hubid })}`);
return response.data.data;
};
// Confirmed against jupiter2doormile.md (Status: Done) as the replacement for
// jupiter's getlocationsummary — per-tenant-site report breakdown. Not in
// express-console-api.md.
export const getLocationsSummary = async (tenantid, locationid, from, to) => {
const response = await doormileAxios.get(`/admin/locations/summary${buildQuery({ tenantid, locationid, from, to })}`);
return response.data.data;
};
export const getProfile = async () => {
const response = await doormileAxios.get('/admin/profile');
return response.data.data;
};
export const updateProfilePassword = async (current_password, new_password) => {
const response = await doormileAxios.put('/admin/profile/password', { current_password, new_password });
return response.data;
};
// ==============================|| App users (staff logins) ||============================== //
export const getAppUsers = async () => {
const response = await doormileAxios.get('/admin/users');
return response.data.data;
};
export const createAppUser = async (data) => {
const response = await doormileAxios.post('/admin/users', data);
return response.data;
};
export const updateAppUser = async (id, data) => {
const response = await doormileAxios.put(`/admin/users/${id}`, data);
return response.data;
};
export const deleteAppUser = async (id) => {
const response = await doormileAxios.delete(`/admin/users/${id}`);
return response.data;
};
// ==============================|| Partners (fleet / rider suppliers) ||============================== //
export const getPartners = async () => {
const response = await doormileAxios.get('/admin/partners');
return response.data.data;
};
export const createPartner = async (data) => {
const response = await doormileAxios.post('/admin/partners', data);
return response.data;
};
export const getPartner = async (id) => {
const response = await doormileAxios.get(`/admin/partners/${id}`);
return response.data.data;
};
export const updatePartner = async (id, data) => {
const response = await doormileAxios.put(`/admin/partners/${id}`, data);
return response.data;
};
export const deletePartner = async (id) => {
const response = await doormileAxios.delete(`/admin/partners/${id}`);
return response.data;
};
// ==============================|| Tenants (client companies) ||============================== //
export const getAdminTenants = async () => {
const response = await doormileAxios.get('/admin/tenants');
return response.data.data;
};
export const createAdminTenant = async (data) => {
const response = await doormileAxios.post('/admin/tenants', data);
return response.data;
};
export const getAdminTenant = async (id) => {
const response = await doormileAxios.get(`/admin/tenants/${id}`);
return response.data.data;
};
export const updateAdminTenant = async (id, data) => {
// requiredeliveryotp is a pointer server-side — omit the key entirely to leave the setting alone.
const response = await doormileAxios.put(`/admin/tenants/${id}`, data);
return response.data;
};
export const deleteAdminTenant = async (id) => {
const response = await doormileAxios.delete(`/admin/tenants/${id}`);
return response.data;
};
export const getTenantLocations = async (tenantId) => {
const response = await doormileAxios.get(`/admin/tenants/${tenantId}/locations`);
return response.data.data;
};
export const createTenantLocation = async (tenantId, data) => {
const response = await doormileAxios.post(`/admin/tenants/${tenantId}/locations`, data);
return response.data;
};
export const updateTenantLocation = async (locationId, data) => {
// Not nested under the tenant — PUT /admin/tenantlocations/:id.
const response = await doormileAxios.put(`/admin/tenantlocations/${locationId}`, data);
return response.data;
};
// ==============================|| Tenant customers (a client's own end customers) ||============================== //
export const getTenantCustomers = async () => fetchAllPages('/admin/tenantcustomers');
export const createTenantCustomer = async (data) => {
const response = await doormileAxios.post('/admin/tenantcustomers', data);
return response.data;
};
export const getTenantCustomer = async (id) => {
const response = await doormileAxios.get(`/admin/tenantcustomers/${id}`);
return response.data.data;
};
export const updateTenantCustomer = async (id, data) => {
const response = await doormileAxios.put(`/admin/tenantcustomers/${id}`, data);
return response.data;
};
export const deleteTenantCustomer = async (id) => {
const response = await doormileAxios.delete(`/admin/tenantcustomers/${id}`);
return response.data;
};
// ==============================|| B2C app customers ||============================== //
export const getAdminCustomers = async () => fetchAllPages('/admin/customers');
// POST /admin/customers.
//
// express-console-api.md lists this resource as GET + PATCH only, and states
// that a customer there comes into existence through a booking. Added on
// explicit instruction to create into the store the Customers page actually
// reads. If the route genuinely does not exist the call returns 404/405, and
// the assistant surfaces that verbatim with the endpoint name rather than
// failing silently — so an unsupported route is immediately visible.
//
// Body mirrors this resource's own GET shape.
export const createAdminCustomer = async (data) => {
const response = await doormileAxios.post('/admin/customers', data);
return response.data;
};
export const updateAdminCustomer = async (id, data) => {
const response = await doormileAxios.patch(`/admin/customers/${id}`, data);
return response.data;
};
// ==============================|| Hubs ||============================== //
export const getHubs = async () => {
const response = await doormileAxios.get('/admin/hubs');
return response.data.data;
};
export const createHub = async (data) => {
const response = await doormileAxios.post('/admin/hubs', data);
return response.data;
};
export const getHub = async (id) => {
const response = await doormileAxios.get(`/admin/hubs/${id}`);
return response.data.data;
};
export const updateHub = async (id, data) => {
const response = await doormileAxios.put(`/admin/hubs/${id}`, data);
return response.data;
};
export const deleteHub = async (id) => {
// Soft delete server-side.
const response = await doormileAxios.delete(`/admin/hubs/${id}`);
return response.data;
};
// ==============================|| Vehicles ||============================== //
export const getVehicles = async () => {
const response = await doormileAxios.get('/admin/vehicles');
return response.data.data;
};
export const createVehicle = async (data) => {
const response = await doormileAxios.post('/admin/vehicles', data);
return response.data;
};
export const getVehicle = async (id) => {
const response = await doormileAxios.get(`/admin/vehicles/${id}`);
return response.data.data;
};
export const updateVehicle = async (id, data) => {
const response = await doormileAxios.put(`/admin/vehicles/${id}`, data);
return response.data;
};
export const deleteVehicle = async (id) => {
// Soft delete server-side.
const response = await doormileAxios.delete(`/admin/vehicles/${id}`);
return response.data;
};
// ==============================|| Milers (riders) ||============================== //
export const getMilers = async () => {
// Tenant-scoped server-side from the token — no tenantid param needed/accepted.
const response = await doormileAxios.get('/admin/milers');
return response.data.data;
};
export const createMiler = async (data) => {
// configid defaults to 1001 server-side (the miler-app login partition) — do not override it.
const response = await doormileAxios.post('/admin/milers', data);
return response.data;
};
export const getMiler = async (id) => {
const response = await doormileAxios.get(`/admin/milers/${id}`);
return response.data.data;
};
export const updateMiler = async (id, data) => {
const response = await doormileAxios.put(`/admin/milers/${id}`, data);
return response.data;
};
export const blockMiler = async (id, data) => {
const response = await doormileAxios.put(`/admin/milers/${id}/block`, data);
return response.data;
};
export const assignMilerVehicle = async (id, data) => {
const response = await doormileAxios.put(`/admin/milers/${id}/assign-vehicle`, data);
return response.data;
};
export const notifyMiler = async (milerProfileId, title, message) => {
const response = await doormileAxios.post(`/admin/milers/${milerProfileId}/notify`, { title, message });
return response.data;
};
// Confirmed against jupiter2doormile.md (Status: Done — tested against a real
// request), not in express-console-api.md. jupiter used fromdate/todate;
// Doormile uses from/to (both YYYY-MM-DD). Aggregate rider KPI counts +
// current position (currentlatitude/currentlongitude/lastpingat per the doc —
// unconfirmed beyond those three field names).
export const getMilerSummary = async (applocationid, from, to, tenantid, hubid) => {
const response = await doormileAxios.get(`/admin/milers/summary${buildQuery({ applocationid, from, to, tenantid, hubid })}`);
return response.data.data;
};
// Replaces jupiter's `riderlogs` table — the rider's GPS/status ping history.
export const getMilerLogs = async (id, from, to, limit) => {
const response = await doormileAxios.get(`/admin/milers/${id}/logs${buildQuery({ from, to, limit })}`);
return response.data.data;
};
// No jupiter equivalent — new in Doormile.
export const getMilerActivity = async (id, from, to) => {
const response = await doormileAxios.get(`/admin/milers/${id}/activity${buildQuery({ from, to })}`);
return response.data.data;
};
// ==============================|| Bookings ||============================== //
export const getBookings = async (pageno, pagesize) => {
const response = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno, pagesize })}`);
return response.data.data;
};
// Same request as getBookings, but keeps the envelope's `total`/`page` instead
// of throwing them away.
//
// getBookings returns `response.data.data` — just the rows — which means no
// caller can tell a full result from a truncated one. `pagesize` is capped at
// 1000 server-side (express-console-api.md, "Conventions": default 500, cap
// 1000), so any account with more than 1000 bookings silently gets a partial
// list from a single call. That's tolerable for a paginated table (it fetches
// the next page as you scroll) but NOT for anything that counts or sums, which
// would report a confidently wrong number.
//
// Kept as a separate export rather than changing getBookings' return shape:
// four callers (fetchDeliveries, fetchCountAPI, orders.js, the assistant's own
// per-order lookup) already destructure it as a bare array.
export const getBookingsPage = async (pageno, pagesize) => {
const response = await doormileAxios.get(`/admin/bookings${buildQuery({ pageno, pagesize })}`);
const body = response.data || {};
return {
rows: body.data || [],
// `total` is documented as present on list responses; fall back to the row
// count so a backend that omits it degrades to "this is everything" rather
// than to NaN-driven pagination.
total: Number.isFinite(Number(body.total)) ? Number(body.total) : (body.data || []).length,
page: Number(body.page) || pageno
};
};
export const createExpressBooking = async (data) => {
// tenantid is forced to the caller's own tenant server-side on client logins.
// CityGate applies: pickup pincode prefix must be an open city (641/600/560/500/629).
const response = await doormileAxios.post('/admin/expressbooking', data);
return response.data;
};
export const createExpressBookingBulk = async (bookings) => {
// Max 200 per call; response carries per-row results.
const response = await doormileAxios.post('/admin/expressbooking/bulk', { bookings });
return response.data;
};
export const getBooking = async (id) => {
const response = await doormileAxios.get(`/admin/bookings/${id}`);
return response.data.data;
};
export const assignMilerToBooking = async (id, data) => {
const response = await doormileAxios.post(`/admin/bookings/${id}/assign-miler`, data);
return response.data;
};
// Doormile-native bulk assign: picks real milers via Redis GEO + AI scoring, no
// external solver involved. Commits server-side in this one call — unlike the
// workolik solver flow, there's no separate preview/reconcile/commit step.
// Sequencing (POST /optimization/doormile/sequence) is not deployed yet, so
// riders with more than one stop come back unsequenced (step: 0) — see
// doormile-flow.md's State table.
export const batchAssignBookings = async (bookingIds, maxPerRider = 5) => {
const response = await doormileAxios.post('/hub/bookings/batch-assign', {
bookingids: bookingIds,
max_per_rider: maxPerRider
});
return response.data;
};
export const assignVehicleToBooking = async (id, data) => {
const response = await doormileAxios.post(`/admin/bookings/${id}/assign-vehicle`, data);
return response.data;
};
export const updateBookingStatus = async (id, data) => {
const response = await doormileAxios.put(`/admin/bookings/${id}/status`, data);
return response.data;
};
export const cancelBooking = async (id, data) => {
const response = await doormileAxios.post(`/admin/bookings/${id}/cancel`, data);
return response.data;
};
export const bulkCancelBookings = async (bookingIds) => {
const response = await doormileAxios.post('/admin/bookings/bulk-cancel', { bookingIds });
return response.data;
};
// Confirmed against jupiter2doormile.md (Status: Done), not in
// express-console-api.md.
export const getBookingTrack = async (id) => {
const response = await doormileAxios.get(`/admin/bookings/${id}/track`);
return response.data.data;
};
// ==============================|| Consignments ||============================== //
export const getConsignments = async () => {
const response = await doormileAxios.get('/admin/consignments');
return response.data.data;
};
// Confirmed against jupiter2doormile.md (Status: Done) — the actual GPS/status
// trail for a consignment, not in express-console-api.md.
export const getConsignmentLogs = async (id) => {
const response = await doormileAxios.get(`/admin/consignments/${id}/logs`);
return response.data.data;
};
export const getConsignment = async (id) => {
const response = await doormileAxios.get(`/admin/consignments/${id}`);
return response.data.data;
};
export const trackConsignment = async (trackingno) => {
const response = await doormileAxios.get(`/admin/consignments/track/${trackingno}`);
return response.data.data;
};
export const updateConsignmentStatus = async (id, data) => {
const response = await doormileAxios.put(`/admin/consignments/${id}/status`, data);
return response.data;
};
// ==============================|| Tripsheets (hub-to-hub transport) ||============================== //
export const getTripsheets = async () => {
const response = await doormileAxios.get('/admin/tripsheets');
return response.data.data;
};
export const createTripsheet = async (data) => {
const response = await doormileAxios.post('/admin/tripsheets', data);
return response.data;
};
export const getTripsheet = async (id) => {
const response = await doormileAxios.get(`/admin/tripsheets/${id}`);
return response.data.data;
};
export const addTripsheetItem = async (id, consignmentid) => {
const response = await doormileAxios.post(`/admin/tripsheets/${id}/items`, { consignmentid });
return response.data;
};
export const removeTripsheetItem = async (id, itemId) => {
const response = await doormileAxios.delete(`/admin/tripsheets/${id}/items/${itemId}`);
return response.data;
};
export const dispatchTripsheet = async (id) => {
const response = await doormileAxios.put(`/admin/tripsheets/${id}/dispatch`);
return response.data;
};
export const arriveTripsheet = async (id) => {
const response = await doormileAxios.put(`/admin/tripsheets/${id}/arrive`);
return response.data;
};
// ==============================|| Pricing ||============================== //
export const getAdminPricing = async () => {
const response = await doormileAxios.get('/admin/pricing');
return response.data.data;
};
export const createAdminPricing = async (data) => {
const response = await doormileAxios.post('/admin/pricing', data);
return response.data;
};
export const updateAdminPricing = async (id, data) => {
const response = await doormileAxios.put(`/admin/pricing/${id}`, data);
return response.data;
};
export const deleteAdminPricing = async (id) => {
const response = await doormileAxios.delete(`/admin/pricing/${id}`);
return response.data;
};
export const simulatePricing = async (data) => {
const response = await doormileAxios.post('/admin/pricing/simulate', data);
return response.data;
};
export const quotePricing = async (data) => {
// Same handler as simulate.
const response = await doormileAxios.post('/admin/pricing/quote', data);
return response.data;
};
export const getDoormilePricing = async () => {
const response = await doormileAxios.get('/admin/doormile-pricing');
return response.data.data;
};
export const createDoormilePricing = async (data) => {
const response = await doormileAxios.post('/admin/doormile-pricing', data);
return response.data;
};
export const updateDoormilePricing = async (id, data) => {
const response = await doormileAxios.put(`/admin/doormile-pricing/${id}`, data);
return response.data;
};
export const deleteDoormilePricing = async (id) => {
// Soft delete server-side.
const response = await doormileAxios.delete(`/admin/doormile-pricing/${id}`);
return response.data;
};
// ==============================|| Exceptions ||============================== //
export const getExceptions = async () => {
const response = await doormileAxios.get('/admin/exceptions');
return response.data.data;
};
export const createException = async (data) => {
const response = await doormileAxios.post('/admin/exceptions', data);
return response.data;
};
export const getException = async (id) => {
const response = await doormileAxios.get(`/admin/exceptions/${id}`);
return response.data.data;
};
export const updateExceptionStatus = async (id, status, resolution) => {
// status: 'Resolved' | 'Closed'.
const response = await doormileAxios.put(`/admin/exceptions/${id}/status`, { status, resolution });
return response.data;
};
// ==============================|| Competitive intel ||============================== //
// Confirmed live: this resource paginates on page/limit, not the
// pageno/pagesize convention used everywhere else in the API — and defaults
// to only 50 rows (there are 339+ competitor branches on a real tenant), so
// callers must page through it explicitly rather than trusting one fetch to
// return everything. Returns the full envelope (data/limit/page/total) so
// the caller can build pagination controls.
export const getCompetitorBranches = async (page, limit) => {
const response = await doormileAxios.get(`/admin/competitor-branches${buildQuery({ page, limit })}`);
return response.data;
};
export const createCompetitorBranch = async (data) => {
const response = await doormileAxios.post('/admin/competitor-branches', data);
return response.data;
};
export const updateCompetitorBranch = async (id, data) => {
const response = await doormileAxios.put(`/admin/competitor-branches/${id}`, data);
return response.data;
};
export const deleteCompetitorBranch = async (id) => {
const response = await doormileAxios.delete(`/admin/competitor-branches/${id}`);
return response.data;
};
// Same page/limit pagination convention as competitor-branches (confirmed
// live) — 368+ rows on a real tenant, default page returns only 100.
export const getCarrierPricing = async (page, limit) => {
const response = await doormileAxios.get(`/admin/carrier-pricing${buildQuery({ page, limit })}`);
return response.data;
};
export const createCarrierPricing = async (data) => {
const response = await doormileAxios.post('/admin/carrier-pricing', data);
return response.data;
};
export const updateCarrierPricing = async (id, data) => {
const response = await doormileAxios.put(`/admin/carrier-pricing/${id}`, data);
return response.data;
};
export const deleteCarrierPricing = async (id) => {
const response = await doormileAxios.delete(`/admin/carrier-pricing/${id}`);
return response.data;
};