diff --git a/.env b/.env index f97a712..db44804 100644 --- a/.env +++ b/.env @@ -6,9 +6,10 @@ REACT_APP_API_URL=https://mock-data-api-nextjs.vercel.app/ ## Google Map Key -REACT_APP_URL=https://jupiter.nearle.app/live/api/v1 -REACT_APP_URL2=https://jupiter.nearle.app/live/api/v2 -REACT_APP_URL3=https://jupiter.nearle.app/live/api/v3 +REACT_APP_URL=https://api.doormile.com/api/v1 +REACT_APP_URL2=https://api.doormile.com/api/v1 +REACT_APP_URL3=https://api.doormile.com/api/v1 +REACT_APP_INTERNAL_KEY=doormile-internal-2024 REACT_APP_STAFF_TOKEN= REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8 REACT_APP_RIDER_ACCESS_ID=AAAAILMpCEU:APA91bEavuOllBI6sFgYtxXAgNmAVwNA-MnCMHLGlR4_t7UqpLajAkdn3T0CZr_zaLBknLyim9ytFLMZgbeXmKqTad_PKCbqlYjHpaizVrLXtecxqyEy4UktIacK2UvHVUATHL-7VQQk diff --git a/.env.development b/.env.development index a174436..8e5e2d0 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,7 @@ -REACT_APP_URL=https://jupiter.nearle.app/live/api/v1 -REACT_APP_URL2=https://jupiter.nearle.app/live/api/v2 -REACT_APP_URL3=https://jupiter.nearle.app/live/api/v3 +REACT_APP_URL=https://api.doormile.com/api/v1 +REACT_APP_URL2=https://api.doormile.com/api/v1 +REACT_APP_URL3=https://api.doormile.com/api/v1 +REACT_APP_INTERNAL_KEY=doormile-internal-2024 REACT_APP_STAFF_TOKEN= REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8 REACT_APP_RIDER_ACCESS_ID=AAAAILMpCEU:APA91bEavuOllBI6sFgYtxXAgNmAVwNA-MnCMHLGlR4_t7UqpLajAkdn3T0CZr_zaLBknLyim9ytFLMZgbeXmKqTad_PKCbqlYjHpaizVrLXtecxqyEy4UktIacK2UvHVUATHL-7VQQk diff --git a/.env.staging b/.env.staging index 675c68a..0677869 100644 --- a/.env.staging +++ b/.env.staging @@ -1,5 +1,6 @@ -REACT_APP_URL=https://jupiter.nearle.app/live/api/v1 -REACT_APP_URL2= +REACT_APP_URL=https://api.doormile.com/api/v1 +REACT_APP_URL2=https://api.doormile.com/api/v1 +REACT_APP_INTERNAL_KEY=doormile-internal-2024 REACT_APP_STAFF_TOKEN= GENERATE_SOURCEMAP=false diff --git a/.gitignore b/.gitignore index e6ba584..d3b9d7c 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,11 @@ dist # wincompare file *.bak + +# Env files — only env.staging (no leading dot) is the agreed-shared +# baseline and gets committed. Everything else holds live API keys. +.env +.env.development +.env.staging +.env.production +.env.local diff --git a/env.staging b/env.staging index 9afabf2..908084c 100644 --- a/env.staging +++ b/env.staging @@ -1,3 +1,4 @@ -REACT_APP_URL='https://jupiter.nearle.app/live/api/v1' -REACT_APP_URL2='' +REACT_APP_URL='https://api.doormile.com/api/v1' +REACT_APP_URL2='https://api.doormile.com/api/v1' +REACT_APP_INTERNAL_KEY='doormile-internal-2024' REACT_APP_STAFF_TOKEN='' \ No newline at end of file diff --git a/src/index.js b/src/index.js index f2c90d2..6f4db0a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ import logger from './utils/logger'; +import axios from 'axios'; import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; @@ -19,6 +20,11 @@ import { store } from 'store'; import reportWebVitals from './reportWebVitals'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +const storedToken = localStorage.getItem('token'); +if (storedToken) { + axios.defaults.headers.common.Authorization = `Bearer ${storedToken}`; +} + const container = document.getElementById('root'); const root = createRoot(container); const queryClient = new QueryClient({ diff --git a/src/pages/api/api.js b/src/pages/api/api.js index 16726f3..3b9e6e7 100644 --- a/src/pages/api/api.js +++ b/src/pages/api/api.js @@ -4,82 +4,96 @@ import dayjs from 'dayjs'; const userid = localStorage.getItem('userid'); // ==============================|| getRiderPeriodicLogs ||============================== // -// Returns the rider's latest periodic log entry — battery, GPS, status, current -// order. Used by the Rider Info modal on the Dispatch page. +// Returns the miler's latest known position/status. Doormile has no periodic-log +// stream yet (that lands with EMQX) — this reads the live snapshot off /admin/milers/:id +// instead. battery/speed have no Doormile equivalent yet, so they're always null. export const getRiderPeriodicLogs = async (userid) => { - const url = `${process.env.REACT_APP_URL}/utils/getriderperiodiclogs${userid ? `?userid=${userid}` : ''}`; - const response = await axios.get(url); - if (response.data && response.data.status) return response.data.data; - return null; + if (!userid) return null; + const response = await axios.get(`${process.env.REACT_APP_URL}/admin/milers/${userid}`); + const m = response.data?.data; + if (!m) return null; + return { + userid: m.userid, + lat: m.currentlat, + lon: m.currentlon, + status: m.availabilitystatus, + battery: null, + speed: null + }; }; // ==============================|| fetchAppLocations||============================== // export const fetchAppLocations = async () => { - const response = await axios.get(`${process.env.REACT_APP_URL}/partners/getlocations/?userid=${userid}`); - const updatedLocations = [ - ...response.data.details, - { locationname: 'All', applocationid: 0 } // Add your new object here - ]; - - return updatedLocations; + const response = await axios.get(`${process.env.REACT_APP_URL}/admin/hubs`); + const hubs = (response.data?.data || []).map((h) => ({ + ...h, + applocationid: h.hubid, + locationname: h.hubname + })); + return [...hubs, { locationname: 'All', applocationid: 0 }]; }; // ==============================|| fetchPercentageData (orders) ||============================== // export const fetchPercentageData = async ({ queryKey }) => { const [, appId, startdate, enddate, tenantid, locationid] = queryKey; - const response = await axios.get( - `${process.env.REACT_APP_URL}/orders/getordersummary/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&fromdate=${startdate}&todate=${enddate}` + const statuses = ['Pending_Pickup', 'Miler_Assigned', 'Delivered', 'Cancelled']; + const [pending, assigned, delivered, cancelled] = await Promise.all( + statuses.map((status) => axios.get(`${process.env.REACT_APP_URL}/admin/bookings`, { params: { status } })) ); - const details = response.data.details; + const created = pending.data.total + assigned.data.total + delivered.data.total + cancelled.data.total; return { - created: details.created.toString(), - uncoveredOrders: details.pending.toString(), - coveredOrders: details.delivered.toString(), - cancelled: details.cancelled.toString(), - percentage1: (Math.round((details.created / details.total) * 100) || 0).toString(), - percentage2: (Math.round((details.pending / details.total) * 100) || 0).toString(), - percentage3: (Math.round((details.delivered / details.total) * 100) || 0).toString(), - percentage4: (Math.round((details.cancelled / details.total) * 100) || 0).toString() + created: created.toString(), + uncoveredOrders: pending.data.total.toString(), + coveredOrders: delivered.data.total.toString(), + cancelled: cancelled.data.total.toString(), + percentage1: (Math.round((created / created) * 100) || 0).toString(), + percentage2: (Math.round((pending.data.total / created) * 100) || 0).toString(), + percentage3: (Math.round((delivered.data.total / created) * 100) || 0).toString(), + percentage4: (Math.round((cancelled.data.total / created) * 100) || 0).toString() }; }; // ===================================================== || getTenants || ===================================================== - -export const getTenants = async (appId) => { - const response = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenants/?applocationid=${appId}&status=active`); - if (response.data.status) { - let arr = []; - response.data.details.map((val) => { - arr.push({ - ...val, - label: `${val.tenantname}` - }); - }); - return arr; - } +// appId (hub/zone) has no equivalent filter on Doormile's CRM clients endpoint — +// kept as a parameter only so existing call sites (deliveries.js, orders.js, +// reports/*) don't need to change their queryFn wiring. +export const getTenants = async () => { + const response = await axios.get(`${process.env.REACT_APP_URL}/crm/clients`); + return (response.data?.data || []).map((val) => ({ + ...val, + tenantid: val.clientid, + tenantname: val.clientname, + label: val.clientname + })); }; // ============================================= || gettenantlocations (branches) || ============================================= -export const gettenantlocations = async (appId) => { - try { - const res = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantlocations/?tenantid=${appId}`); - return res.data.details; - } catch (err) { - console.log('gettenantlocations', err); - } +// No Doormile equivalent to tenant branches — call sites use this for a +// branch/location dropdown that doesn't apply to Doormile's client model. +export const gettenantlocations = async () => { + return []; }; // ==============================|| fetchorderscount (orders) ||============================== // export const fetchorderscount = async ({ queryKey }) => { + // eslint-disable-next-line no-unused-vars const [, appId, startdate, enddate, currentStatus, tenantid, locationid] = queryKey; - const url = `${process.env.REACT_APP_URL}/orders/getordersummary/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&fromdate=${startdate}&todate=${enddate}&status=${currentStatus}`; + const [all, pending, delivered, cancelled] = await Promise.all([ + axios.get(`${process.env.REACT_APP_URL}/admin/bookings`), + axios.get(`${process.env.REACT_APP_URL}/admin/bookings`, { params: { status: 'Pending_Pickup' } }), + axios.get(`${process.env.REACT_APP_URL}/admin/bookings`, { params: { status: 'Delivered' } }), + axios.get(`${process.env.REACT_APP_URL}/admin/bookings`, { params: { status: 'Cancelled' } }) + ]); - const response = await axios.get(url); - - return response.data.details; + return { + created: all.data.total, + pending: pending.data.total, + delivered: delivered.data.total, + cancelled: cancelled.data.total + }; }; // ==============================|| fetchOrders (orders) ||============================== // @@ -95,15 +109,21 @@ export const fetchorderscount = async ({ queryKey }) => { // return response.data.details.map((val, i) => ({ ...val, sno: i + 1 })); // }; export const fetchOrders = async ({ pageParam = 1, queryKey }) => { + // eslint-disable-next-line no-unused-vars const [, appId, currentStatus, debouncedSearch, startdate, enddate, rowsPerPage, tenantid, locationid] = queryKey; - const url = `${process.env.REACT_APP_URL}/orders/tenant/getorders/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&status=${currentStatus}&fromdate=${startdate}&todate=${enddate}&keyword=${debouncedSearch}&pageno=${pageParam}&pagesize=${rowsPerPage}`; - - const response = await axios.get(url); + const response = await axios.get(`${process.env.REACT_APP_URL}/admin/bookings`, { + params: { + status: currentStatus === 'All' ? undefined : currentStatus, + keyword: debouncedSearch, + pageno: pageParam, + pagesize: rowsPerPage + } + }); return { - rows: response.data.details, - nextPage: response.data.details.length === Number(rowsPerPage) ? pageParam + 1 : undefined + rows: response.data.data, + nextPage: response.data.data.length === Number(rowsPerPage) ? pageParam + 1 : undefined }; }; @@ -137,6 +157,46 @@ export const fetchRidersList = async ({ queryKey }) => { } }; +// ==============================|| Doormile assignment (new) ||============================== // +// NOTE: createOptimisationDeliveries / reconcileSteps / fetchBatchEfficiency / +// finalCreatedeliveries / createAutomationDeliveries below still call the legacy +// NearlExpress optimiser (routes.workolik.com / routemate.workolik.com / +// jupiter.nearle.app) and are wired into Dispatch.js, Preview.js, OrdersPreview.js, +// orders.js and deliveries.js. Per the "don't touch Dispatch/Preview internals — +// Phase 3" instruction, they're left as-is rather than renamed/repointed, since +// doing so would silently break those pages' running mutations without a +// corresponding page rewrite. The Doormile-native replacements are added here +// instead, so Phase 3 can cut individual call sites over one at a time. + +// assignBooking — triggers Doormile's AI assignment engine for one booking via +// the internal-key-gated endpoint. SECURITY: X-Internal-Key is a static secret +// baked into this client bundle — anyone can extract it from the shipped JS and +// call this endpoint directly. Prefer routing this through the already- +// authenticated admin JWT (see autoAssignBooking below) once the backend +// supports it; keep this only if /internal/bookings/:id/reassign truly must +// stay key-gated rather than JWT-gated. +export const assignBooking = async (bookingId) => { + const response = await axios.post( + `${process.env.REACT_APP_URL}/internal/bookings/${bookingId}/reassign`, + {}, + { headers: { 'X-Internal-Key': process.env.REACT_APP_INTERNAL_KEY } } + ); + return response.data; +}; + +// autoAssignBooking — same intent as assignBooking but authenticated with the +// operator's own admin JWT instead of a shared static key. +export const autoAssignBooking = async (bookingId) => { + const response = await axios.post(`${process.env.REACT_APP_URL}/hub/bookings/${bookingId}/auto-assign`, {}); + return response.data; +}; + +// fetchDashboardStats — hub-level stats, replaces the workolik batch-efficiency call. +export const fetchDashboardStats = async () => { + const response = await axios.get(`${process.env.REACT_APP_URL}/hub/dashboard`); + return response.data; +}; + // ==============================|| createOptimisationDeliveries (orders) Arrange the order ||============================== // export const createOptimisationDeliveries = async (deliveryData) => { @@ -145,14 +205,9 @@ export const createOptimisationDeliveries = async (deliveryData) => { return response.data; }; // ==============================|| reconcileSteps (Preview - validate rider/order step assignments) ||============================== // - -export const reconcileSteps = async ({ riders }) => { - const response = await axios.post( - `https://routes.workolik.com/api/v1/optimization/reconcile-steps`, - { riders } - ); - return response.data; -}; +// No Doormile equivalent — made a no-op so CLAUDE.md's "always reconcile before +// createdeliveries" hard constraint keeps holding trivially until Phase 3. +export const reconcileSteps = async (data) => data; // ==============================|| fetchBatchEfficiency (Dispatch - Analysis view) ||============================== // // Calls POST /api/v1/batch/efficiency with a JSON body { batch, tenant_id }. @@ -223,24 +278,9 @@ export const createAutomationDeliveries = async (variables) => { }; // ==============================|| notifyRider (orders / deliveries) ||============================== // - -export const notifyRider = async (riderToken) => { - if (!riderToken) { - throw new Error('Invalid rider token'); - } - console.log('notify rider called'); - console.log('riderToken', riderToken); - const response = await axios.post(`${process.env.REACT_APP_URL}/utils/notifyuser`, { - token: riderToken, - notification: { - title: 'NearleXpress', - body: 'Orders have been placed for delivery. Kindly accept and process deliveries', - sound: 'ring', - image: '' - } - }); - return response.data; -}; +// Doormile sends miler FCM notifications automatically from the Go backend on +// status changes, so there's no equivalent client-triggered endpoint. No-op. +export const notifyRider = async () => ({ success: true }); // ==============================|| cancelOrder (orders) ||============================== // @@ -270,18 +310,29 @@ export const cancelMultipleOrder = async (orderlist) => { }; // ==============================|| fetchDeliveries (deliveries) ||============================== // +// NOTE: NearlExpress "deliveries" tabs are keyed off statuses +// (pending/accepted/arrived/picked/active/delivered/cancelled/skipped) that +// don't exist in Doormile's booking status vocabulary (Pending_Pickup, +// Miler_Assigned, Pickup_Scheduled, At_Customer, Picked_Up, At_Hub, Delivered, +// Cancelled, Assignment_Failed). This passes currentStatus straight through — +// deliveries.js's own tab definitions still need updating to Doormile statuses +// (flagged, not guessed). export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => { + // eslint-disable-next-line no-unused-vars let [, appId, userid, currentStatus, startdate, enddate, rowsPerPage, searchword, tenantid, locationid, riderid] = queryKey; - currentStatus = currentStatus == 'All' ? 'all' : currentStatus; - const url = - appId === 0 - ? `${process.env.REACT_APP_URL}/deliveries/getdeliveries/?appuserid=${userid}&status=${currentStatus}&fromdate=${startdate}&todate=${enddate}&pageno=${pageParam}&pagesize=${rowsPerPage}&keyword=${searchword}&tenantid=${tenantid}&locationid=${locationid}&userid=${riderid}` - : `${process.env.REACT_APP_URL}/deliveries/getdeliveries/?applocationid=${appId}&status=${currentStatus}&fromdate=${startdate}&todate=${enddate}&pageno=${pageParam}&pagesize=${rowsPerPage}&keyword=${searchword}&tenantid=${tenantid}&locationid=${locationid}&userid=${riderid}`; - const response = await axios.get(url); + + const response = await axios.get(`${process.env.REACT_APP_URL}/admin/bookings`, { + params: { + status: currentStatus === 'all' || currentStatus === 'All' ? undefined : currentStatus, + keyword: searchword, + pageno: pageParam, + pagesize: rowsPerPage + } + }); return { - rows: response.data.details, - nextPage: response.data.details.length === Number(rowsPerPage) ? pageParam + 1 : undefined + rows: response.data.data, + nextPage: response.data.data.length === Number(rowsPerPage) ? pageParam + 1 : undefined }; }; @@ -370,32 +421,46 @@ export const updateDeliveryAPI = async (orderData) => { }; // ==============================|| getalltenants (tenants) ||============================== // - +// Doormile's /crm/clients has no documented status/keyword/page filter params +// (unlike the old /tenants/getalltenants), so this fetches the full client +// list and filters + paginates client-side. export const getalltenants = async ({ queryKey }) => { - const [, appId, debouncedSearch, status, page, rowsPerPage] = queryKey; + const [, , debouncedSearch, status, page, rowsPerPage] = queryKey; try { - let url = `${process.env.REACT_APP_URL - }/tenants/getalltenants/?status=${status}&applocationid=${appId}&keyword=${debouncedSearch}&pageno=${page + 1 - }&pagesize=${rowsPerPage}&moduleid=6`; - const response = await axios.get(url); - return response.data.details; // return only data, keep it clean + const response = await axios.get(`${process.env.REACT_APP_URL}/crm/clients`); + let clients = (response.data?.data || []).map((c) => ({ + ...c, + tenantid: c.clientid, + tenantname: c.clientname + })); + + if (status) { + clients = clients.filter((c) => (c.status || '').toLowerCase() === status.toLowerCase()); + } + if (debouncedSearch) { + const kw = debouncedSearch.toLowerCase(); + clients = clients.filter((c) => (c.clientname || '').toLowerCase().includes(kw) || (c.email || '').toLowerCase().includes(kw)); + } + + const start = page * rowsPerPage; + return clients.slice(start, start + rowsPerPage); } catch (err) { const message = err.response?.data?.message || err.message || 'Something went wrong'; OpenToast(message); - return null; // return null for failure + return null; } }; // ==============================|| gettenantsummary (tenants) ||============================== // - -export const gettenantsummary = async ({ queryKey }) => { - const [, appId] = queryKey; +export const gettenantsummary = async () => { try { - const response = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantsummary/?moduleid=6&applocationid=${appId}`); - return response.data.summary; // return only data, keep it clean + const response = await axios.get(`${process.env.REACT_APP_URL}/crm/clients`); + const clients = response.data?.data || []; + const countOf = (s) => clients.filter((c) => (c.status || '').toLowerCase() === s).length; + return { active: countOf('active'), pending: countOf('pending'), inactive: countOf('inactive') }; } catch (err) { const message = err.response?.data?.message || err.message || 'Something went wrong'; OpenToast(message); - return null; // return null for failure + return null; } }; // ==============================|| getpricinglist (tenants) ||============================== // @@ -467,55 +532,58 @@ export const getallcustomers = async ({ pageParam = 1, queryKey }) => { }; // ==============================|| fetchAllRiders (riders) ||============================== // -export const fetchAllRiders = async ({ pageParam = 1, queryKey }) => { +// Doormile's /admin/milers has no documented pagination or search/status +// filter params, unlike the old /partners/getallriders. Fetches the full miler +// list and filters client-side; single page (nextPage always undefined) until +// the backend adds server-side paging. +export const fetchAllRiders = async ({ queryKey }) => { try { + // eslint-disable-next-line no-unused-vars const [, appId, debouncedSearch, tabvalue] = queryKey; + const res = await axios.get(`${process.env.REACT_APP_URL}/admin/milers`); + let milers = res.data?.data || []; - const url = `${process.env.REACT_APP_URL - }/partners/getallriders/?applocationid=${appId}&pageno=${pageParam}&pagesize=${20}&keyword=${debouncedSearch}&status=${(tabvalue == 0 || tabvalue == 2) ? '' : 'Active' - }`; - const res = await axios.get(url); - return { - details: res.data.details, - nextPage: res.data.details.length === 20 ? pageParam + 1 : undefined - }; + if (debouncedSearch) { + const kw = debouncedSearch.toLowerCase(); + milers = milers.filter( + (m) => (m.displayname || '').toLowerCase().includes(kw) || (m.phone || '').includes(debouncedSearch) + ); + } + if (tabvalue != 0 && tabvalue != 2) { + milers = milers.filter((m) => m.availabilitystatus === 'Available'); + } + + return { details: milers, nextPage: undefined }; } catch (err) { console.log('fetchAllRiders err', err.message); - return []; + return { details: [], nextPage: undefined }; } }; // ==============================|| getallridersummary (riders) ||============================== // -export const getallridersummary = async ({ queryKey }) => { +export const getallridersummary = async () => { try { - const [, appId, tabvalue] = queryKey; - const response = await axios.get( - `${process.env.REACT_APP_URL}/partners/getallridersummary/?applocationid=${appId}&status=${tabvalue == 0 ? '' : 'Active'}` - ); - return response.data.details; + const response = await axios.get(`${process.env.REACT_APP_URL}/admin/milers`); + const milers = response.data?.data || []; + const active = milers.filter((m) => m.availabilitystatus === 'Available').length; + return { total: milers.length, active, inactive: milers.length - active }; } catch (err) { console.log('getallridersummary err', err.message); - return []; + return { total: 0, active: 0, inactive: 0 }; } }; // ==============================|| fetchRiders (riders), active riders ||============================== // +// Not currently imported anywhere (riders.js uses fetchAllRiders) — updated for +// consistency in case a future page picks it up. +export const fetchRiders = async (appId) => { + const response = await axios.get(`${process.env.REACT_APP_URL}/admin/milers`); + return response.data?.data || []; +}; -export const fetchRiders = async ({ pageParam = 1, queryKey }) => { - try { - const [, appId, debouncedSearch] = queryKey; - - const url = `${process.env.REACT_APP_URL - }/partners/getriders/?applocationid=${appId}&pageno=${pageParam}&pagesize=${20}&keyword=${debouncedSearch}`; - - const res = await axios.get(url); - return { - details: res.data.details, - nextPage: res.data.details.length === 20 ? pageParam + 1 : undefined - }; - } catch (err) { - console.log('fetchRiders err', err.message); - return []; - } +// ==============================|| fetchMilerDetail (riders) ||============================== // +export const fetchMilerDetail = async (milerid) => { + const response = await axios.get(`${process.env.REACT_APP_URL}/admin/milers/${milerid}`); + return response.data.data; }; // ==============================|| getriderstatus (riders)||============================== // diff --git a/src/pages/nearle/clients/Tenants.js b/src/pages/nearle/clients/Tenants.js index 85e0c1e..7e5d8ad 100644 --- a/src/pages/nearle/clients/Tenants.js +++ b/src/pages/nearle/clients/Tenants.js @@ -1,15 +1,9 @@ -import React, { useEffect, useState, useRef, useMemo, Fragment } from 'react'; +import React, { useEffect, useState, Fragment } from 'react'; import MainCard from 'components/MainCard'; import axios from 'axios'; import { useTheme } from '@mui/material/styles'; import Loader from 'components/Loader'; -import { Empty } from 'antd'; -import dayjs from 'dayjs'; import { enqueueSnackbar } from 'notistack'; -import Geocode from 'react-geocode'; -import LocationOnIcon from '@mui/icons-material/LocationOn'; -import parse from 'autosuggest-highlight/parse'; -import { debounce } from '@mui/material/utils'; import { Stack, Table, @@ -22,22 +16,14 @@ import { IconButton, Box, Grid, - Autocomplete, TextField, Tooltip, Collapse, - Tab, - Tabs, Divider, Card, CardContent, CardActions, Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - FormLabel, TablePagination, Skeleton, Avatar, @@ -45,31 +31,12 @@ import { useMediaQuery } from '@mui/material'; import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard'; -import { - EyeOutlined, - EyeInvisibleOutlined, - EditOutlined, - IssuesCloseOutlined, - StopOutlined, - CloseOutlined -} from '@ant-design/icons'; -import { PopupTransition } from 'components/@extended/Transitions'; -import { FaRegCheckCircle, FaRegAddressCard } from 'react-icons/fa'; -import { TbBrandDatabricks, TbWorldLongitude, TbWorldLatitude } from 'react-icons/tb'; -import { GiMoneyStack, GiModernCity } from 'react-icons/gi'; +import { EyeOutlined, EyeInvisibleOutlined, EditOutlined, IssuesCloseOutlined, StopOutlined, CloseOutlined } from '@ant-design/icons'; +import { FaRegCheckCircle } from 'react-icons/fa'; +import { TbBrandDatabricks } from 'react-icons/tb'; +import { GiModernCity } from 'react-icons/gi'; import { FiUser, FiPhoneCall } from 'react-icons/fi'; -import { - MdNumbers, - MdGroups, - MdCheckCircle, - MdHourglassEmpty, - MdCancel, - MdOutlineCheckCircle, - MdOutlinePendingActions, - MdOutlineCancel, - MdMyLocation, - MdPersonPin -} from 'react-icons/md'; +import { MdGroups, MdCheckCircle, MdHourglassEmpty, MdCancel, MdOutlineCheckCircle, MdOutlinePendingActions, MdOutlineCancel, MdMyLocation, MdPersonPin } from 'react-icons/md'; import { LuMail } from 'react-icons/lu'; import { BiUser } from 'react-icons/bi'; import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; @@ -108,21 +75,21 @@ const edge = (c) => a(c, '55'); // Status palette — drives the pill tabs and per-row badges. const STATUS_META = { - active: { label: 'Active', color: '#10b981', icon: MdCheckCircle, statusKey: 'active' }, - pending: { label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty, statusKey: 'pending' }, - inactive: { label: 'Inactive', color: '#ef4444', icon: MdCancel, statusKey: 'inactive' } + active: { label: 'Active', color: '#10b981', icon: MdCheckCircle, statusKey: 'active' }, + pending: { label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty, statusKey: 'pending' }, + inactive: { label: 'Inactive', color: '#ef4444', icon: MdCancel, statusKey: 'inactive' } }; const STATUS_TABS = [ - { status: 'active', countKey: 'active', tabStatus: 'Active' }, - { status: 'pending', countKey: 'pending', tabStatus: 'Pending' }, - { status: 'inactive', countKey: 'inactive', tabStatus: 'InActive' } + { status: 'active', countKey: 'active' }, + { status: 'pending', countKey: 'pending' }, + { status: 'inactive', countKey: 'inactive' } ]; const KPI_META = [ - { key: 'active', label: 'Active Tenants', color: '#10b981', icon: MdOutlineCheckCircle, countKey: 'active' }, - { key: 'pending', label: 'Pending Approval', color: '#f59e0b', icon: MdOutlinePendingActions, countKey: 'pending' }, - { key: 'inactive', label: 'Inactive Tenants', color: '#ef4444', icon: MdOutlineCancel, countKey: 'inactive' } + { key: 'active', label: 'Active Clients', color: '#10b981', icon: MdOutlineCheckCircle, countKey: 'active' }, + { key: 'pending', label: 'Pending Approval', color: '#f59e0b', icon: MdOutlinePendingActions, countKey: 'pending' }, + { key: 'inactive', label: 'Inactive Clients', color: '#ef4444', icon: MdOutlineCancel, countKey: 'inactive' } ]; const SoftPaper = (props) => ( @@ -155,36 +122,21 @@ const AccentAvatar = ({ color, selected, size = 24, children }) => ( // ==============================|| Starts here||============================== // const Clients1 = () => { - const textFieldRef = useRef(null); const [searchword, setSearchword] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [rowsPerPage, setRowsPerPage] = React.useState(10); - // const [tenantList, settenantList] = useState([]); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [isloader, setisloader] = useState(false); const [appId, setAppId] = useState(0); const [locaName, setLocoName] = useState(''); const [locations] = useState('All'); - const [value, setValue] = React.useState(null); const [value0, setValue0] = useState(0); - const [value1, setValue1] = useState(0); - const [value2, setValue2] = useState(0); const [selectedTenid, setSelectedtenid] = useState(null); - const [clientpricelist, setClientpricelist] = useState([]); const [status, setstatus] = useState('active'); const [selectedCustomer, setSelectedCustomer] = useState({}); // to edit - const [dialogopen, setDialogopen] = useState(false); - const [appPricing, setAppPricing] = useState([]); - const [selectedPricing, setSelectedPricing] = useState({}); - const [isPrice, setIsprice] = useState(true); - const [address, setAddress] = useState(''); - const [city, setCity] = useState(''); - const [zipcode, setZipcode] = useState(''); - const [latlong, setLatlong] = useState({}); const [editClient, setEditClient] = useState({}); const [page, setPage] = useState(0); - const [, setTabStatus] = useState('Active'); const handleChangePage = (event, newPage) => { setPage(newPage); @@ -194,24 +146,13 @@ const Clients1 = () => { setRowsPerPage(parseInt(event.target.value, 10)); setPage(0); }; - const dialogclose = () => { - setDialogopen(false); - }; const handleChange = (event, newValue) => { - setTabStatus(newValue == 0 ? 'Active' : newValue == 1 ? 'Pending' : 'InActive'); + setstatus(newValue == 0 ? 'active' : newValue == 1 ? 'pending' : 'inactive'); setValue0(newValue); setSearchword(''); setPage(0); }; - const handleChange1 = (event, newValue1) => { - console.log('newValue1', newValue1); - setValue1(newValue1); - }; - const handleChange2 = (event, newValue2) => { - console.log('newValue2', newValue2); - setValue2(newValue2); - }; const [openRowIndex1, setOpenRowIndex1] = useState(null); // Initially no row is open for collapsible section 1 const [openRowIndex2, setOpenRowIndex2] = useState(null); // Initially no row is open for collapsible section 2 @@ -223,9 +164,6 @@ const Clients1 = () => { const handleCollapseToggle2 = (rowIndex) => { setOpenRowIndex2((prevIndex) => (prevIndex === rowIndex ? null : rowIndex)); }; - useEffect(() => { - console.log('selectedCustomer', selectedCustomer); - }, [selectedCustomer]); useEffect(() => { setOpenRowIndex1(-1); setOpenRowIndex2(-1); @@ -240,171 +178,6 @@ const Clients1 = () => { autoHideDuration: duration }); }; - // ==============================|| google address ||============================== // - const GOOGLE_MAPS_API_KEY = process.env.REACT_APP_GOOGLE_MAPS_API_KEY; - - function loadScript(src, position, id) { - if (!position) { - return; - } - - const script = document.createElement('script'); - script.setAttribute('async', ''); - script.setAttribute('id', id); - script.src = src; - position.appendChild(script); - } - - const autocompleteService = { current: null }; - // ==============================|| for google address ||============================== // - const [inputValue, setInputValue] = useState(''); - const [options, setOptions] = useState([]); - const loaded = useRef(false); - if (typeof window !== 'undefined' && !loaded.current) { - if (!document.querySelector('#google-maps')) { - loadScript( - `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=places`, - document.querySelector('head'), - 'google-maps' - ); - } - - loaded.current = true; - } - - const fetch = useMemo( - () => - debounce((request, callback) => { - autocompleteService.current.getPlacePredictions(request, callback); - }, 400), - [] - ); - useEffect(() => { - let active = true; - if (!autocompleteService.current && window.google) { - autocompleteService.current = new window.google.maps.places.AutocompleteService(); - } - if (!autocompleteService.current) { - return undefined; - } - - if (inputValue === '') { - setOptions(value ? [value] : []); - return undefined; - } - fetch({ input: inputValue }, (results) => { - if (active) { - let newOptions = []; - - if (value) { - newOptions = [value]; - } - - if (results) { - newOptions = [...newOptions, ...results]; - } - setOptions(newOptions); - } - }); - - return () => { - active = false; - }; - }, [value, inputValue, fetch]); - - Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY); - useEffect(() => { - try { - console.log('selected address =>', address); - Geocode.fromAddress(address).then( - (response) => { - console.log('lat long response =>', response.results[0]); - if (response.status == 'OK') { - const { lat, lng } = response.results[0].geometry.location; - setLatlong({ - lat, - lng - }); - setEditClient({ - ...editClient, - latitude: lat.toString(), - longitude: lng.toString() - }); - // setSelectedCustomer({ - // ...selectedCustomer, - // latitude: lat, - // longitude: lng, - // }); - if (response.results[0].address_components) { - let place = response.results[0]; - let city1, zipcode1, state1, suburb1; - for (let i = 0; i < place.address_components.length; i++) { - for (let j = 0; j < place.address_components[i].types.length; j++) { - switch (place.address_components[i].types[j]) { - case 'locality': - city1 = place.address_components[i].long_name; - break; - case 'administrative_area_level_1': - state1 = place.address_components[i].long_name; - break; - case 'postal_code': - zipcode1 = place.address_components[i].long_name; - break; - case 'sublocality': - suburb1 = place.address_components[i].long_name; - break; - } - } - } - setCity(city1 || ''); - setState(state1 || ''); - setZipcode(zipcode1 || ''); - setSuburb(suburb1 || ''); - setEditClient({ - ...editClient, - city: city1 || '', - state: state1 || '', - postcode: zipcode || '', - suburb: suburb1 || '' - }); - } - } - }, - (error) => { - console.log(error); - } - ); - } catch (err) { - console.log(err); - } - }, [address]); - - useEffect(() => { - selectedCustomer && - setLatlong({ - lat: selectedCustomer.latitude, - lng: selectedCustomer.longitude - }); - }, [selectedCustomer]); - /* ============================================= || handleKeyPress (ctrl+k)| ============================================= */ - useEffect(() => { - const handleKeyPress = (event) => { - if (event.key === 'k' && (event.metaKey || event.ctrlKey)) { - event.preventDefault(); - - textFieldRef.current.focus(); - } - if (event.key === 'Escape' && document.activeElement === textFieldRef.current) { - // Remove focus from the TextField - textFieldRef.current.blur(); - } - }; - document.addEventListener('keydown', handleKeyPress); - - return () => { - document.removeEventListener('keydown', handleKeyPress); - }; - }, []); /* ============================================= || getalltenants| ============================================= */ @@ -427,136 +200,42 @@ const Clients1 = () => { queryFn: gettenantsummary }); - /* ============================================= || fetchclientpricelist || ============================================= */ - - const fetchclientpricelist = async () => { - await axios - .get(`${process.env.REACT_APP_URL}/tenants/getpricinglist/?tenantid=${selectedTenid}`) - .then((res) => { - console.log('getpricinglist', res); - if (res.data.status) { - setClientpricelist(res.data.details); - } - }) - .catch((err) => { - console.log(err); - }); - }; - useEffect(() => { - selectedTenid && fetchclientpricelist(); - }, [selectedTenid]); - - /* ============================================= || fetchTenanatPricing || ============================================= */ - const fetchTenanatPricing = async (id) => { - try { - let tenantPricing = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantpricing/?tenantid=${id}`); - console.log('tenantPricing', tenantPricing.data.details); - setTenanatPricing(tenantPricing.data.details); - } catch (error) { - console.log('fetchTenanatPricing', error); - } - }; - useEffect(() => { - selectedTenid && fetchTenanatPricing(selectedTenid); - }, [selectedTenid]); - /* ============================================= || tenantupdate || ============================================= */ - + /* ============================================= || tenantupdate — sets a client active/inactive || ============================================= */ + // Doormile has no separate pricing-approval step: "Approve" for a pending + // client is the same action as "Activate" for an inactive one — both just + // PATCH status to Active. Client pricing lives in the separate Doormile + // Pricing table (clientPricing.js), not here. const tenantupdate = async (tenid) => { setisloader(true); - let updateData; - if (tenid == -1) { - updateData = { - tenantid: selectedTenid, - approved: 1 - }; - } else { - updateData = { tenantid: tenid, status: status === 'active' ? 'InActive' : 'Active' }; - } + const updateData = { status: status === 'active' ? 'InActive' : 'Active' }; await axios - .put(`${process.env.REACT_APP_URL}/tenants/update`, updateData) + .patch(`${process.env.REACT_APP_URL}/crm/clients/${tenid}`, updateData) .then((res) => { - if (res.data.status) { - opentoast( - value0 == 0 ? 'Inactivated Successfully' : value0 == 1 ? 'Approved Successfully' : 'Activate Successfully', - 'success', - 2000 - ); + if (res.data?.success !== false) { + opentoast(value0 == 0 ? 'Inactivated Successfully' : 'Activated Successfully', 'success', 2000); getalltenantsRefetch(); summaryDataRefetch(); - setisloader(false); } - }) - .catch((err) => { - console.log(err); - opentoast(err.message, 'error', 1500); setisloader(false); - }); - // } - }; - /* ============================================= || getAppPricing || ============================================= */ - const getAppPricing = async (id) => { - console.log('id', id); - try { - let appPricingRes = await axios.get(`${process.env.REACT_APP_URL}/utils/getapppricing/?applocationid=${id}`); - console.log('appPricingRes', appPricingRes.data.details); - setAppPricing(appPricingRes.data.details); - } catch (error) { - console.log('appPricingRes', error); - } - }; - /* ============================================= || createpricing || ============================================= */ - const createpricing = async () => { - setisloader(true); - await axios - .post(`${process.env.REACT_APP_URL}/tenants/createpricing`, { - tenantpricingid: 0, - applocationid: appId, - pricingid: selectedPricing.pricingid, - tenantid: selectedTenid, - pricingdate: dayjs().format('YYYY-MM-DD HH:mm:ss'), - configid: selectedPricing.configid, - pricingtypeid: selectedPricing.pricingtypeid, - slab: selectedPricing.slab, - baseprice: +selectedPricing.baseprice, - priceperkm: +selectedPricing.priceperkm, - minkm: +selectedPricing.minkm, - maxkm: +selectedPricing.maxkm, - orders: +selectedPricing.minorder, - othercharges: 0 - }) - .then((res) => { - if (res.data.status) { - enqueueSnackbar('Price Created Successfully', { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - dialogclose(); - setSelectedPricing({}); - tenantupdate(-1); - fetchclientpricelist(); - } }) .catch((err) => { - console.log(err); + opentoast(err.response?.data?.message || err.message, 'error', 1500); + setisloader(false); }); }; /* ============================================= || updateClient || ============================================= */ const updateClient = async () => { try { - const arr = { ...editClient, tenantid: selectedTenid }; - - console.log('updateClient', arr); - const updateRes = await axios.put(`${process.env.REACT_APP_URL}/tenants/update`, arr); - console.log('updateClient', updateRes.data.message); - if (updateRes.data.status) { - opentoast(updateRes.data.message, 'success', 1500); + const arr = { ...editClient }; + const updateRes = await axios.patch(`${process.env.REACT_APP_URL}/crm/clients/${selectedTenid}`, arr); + if (updateRes.data?.success !== false) { + opentoast(updateRes.data?.message || 'Updated Successfully', 'success', 1500); } getalltenantsRefetch(); summaryDataRefetch(); } catch (err) { - console.log('updateClient', err); + opentoast(err.response?.data?.message || err.message, 'error', 1500); } }; @@ -567,7 +246,7 @@ const Clients1 = () => { {(getalltenantsIsLoading || summaryDataIsLoading || isloader) && } {/* ============================================= || Header | ============================================= */} { background: '#fff' }} > - + { > - + {meta.label} { Status Client Contact - Address + City Actions @@ -806,299 +476,203 @@ const Clients1 = () => { - No tenants to show + No clients to show - {`No ${activeTabMeta.label.toLowerCase()} tenants for this filter.`} + {`No ${activeTabMeta.label.toLowerCase()} clients for this filter.`} ) : ( tenantList?.map((row, index) => { - const rowStatusKey = (value0 === 0 ? 'active' : value0 === 1 ? 'pending' : 'inactive'); + const rowStatusKey = value0 === 0 ? 'active' : value0 === 1 ? 'pending' : 'inactive'; const rowStatusMeta = STATUS_META[rowStatusKey]; const RowStatusIcon = rowStatusMeta.icon; return ( - - {/* ============================================ || tablerow 1 || ============================================ */} - - - - {String(index + 1 + page * rowsPerPage).padStart(2, '0')} - - - - - - - - - {rowStatusMeta.label} + + {/* ============================================ || tablerow 1 || ============================================ */} + + + + {String(index + 1 + page * rowsPerPage).padStart(2, '0')} - - - - - - - - - - {row.tenantname} - - - ID #{row.tenantid} + + + + + + + + {rowStatusMeta.label} - - - - - - {row.primarycontact || '—'} - + + + + + + + + + {row.tenantname} + + + ID #{row.tenantid} + + + + + + + + {row.phone || '—'} + + + {row.email || '—'} + + + + - {row.primaryemail || '—'} + {row.city || '—'} - - - - - - {row.address || '—'} - - - - - - {value0 == 0 && ( - - { - setSelectedCustomer(row); - setSelectedtenid(row.tenantid); - setAppId(row.applocationid); - setTimeout(() => { + + + + {value0 == 0 && ( + + { + setSelectedCustomer(row); + setSelectedtenid(row.tenantid); tenantupdate(row.tenantid); - }, 100); - }} - > - - - - )} - {value0 == 1 && ( - - { - setSelectedCustomer(row); - setDialogopen(true); - setSelectedtenid(row.tenantid); - setAppId(row.applocationid); - getAppPricing(row.applolcationid); - }} - > - - - - )} - {value0 == 2 && ( - - { - setSelectedCustomer(row); - setSelectedtenid(row.tenantid); - setAppId(row.applocationid); - setTimeout(() => { - tenantupdate(row.tenantid); - }, 100); - }} - > - - - - )} - - { - setSelectedCustomer(row); - handleCollapseToggle1(index); - setOpenRowIndex2(-1); - setSelectedtenid(row.tenantid); - }} - > - {openRowIndex1 === index ? : } - - - {value0 !== 1 && ( - - { - setSelectedCustomer(row); - handleCollapseToggle2(index); - setOpenRowIndex1(-1); - setSelectedtenid(row.tenantid); - setAppId(row.applocationid); - }} - > - {openRowIndex2 === index ? : } - - - )} - - - - {/* ============================================ || collapsive row (1) (view) || ============================================ */} - {openRowIndex1 === index && ( - - - - - - } - sx={{ - alignItems: 'center', - flexDirection: 'row' - }} - /> - - } - sx={{ - alignItems: 'center', - flexDirection: 'row' - }} - /> - - } - > - {/* ============================================= || Details || ============================================= */} - {value1 == 0 && ( + + + + )} + {(value0 == 1 || value0 == 2) && ( + + { + setSelectedCustomer(row); + setSelectedtenid(row.tenantid); + tenantupdate(row.tenantid); + }} + > + {value0 == 1 ? : } + + + )} + + { + setSelectedCustomer(row); + handleCollapseToggle1(index); + setOpenRowIndex2(-1); + setSelectedtenid(row.tenantid); + }} + > + {openRowIndex1 === index ? : } + + + {value0 !== 1 && ( + + { + setSelectedCustomer(row); + setEditClient({ clientname: row.tenantname, email: row.email, phone: row.phone, city: row.city }); + handleCollapseToggle2(index); + setOpenRowIndex1(-1); + setSelectedtenid(row.tenantid); + }} + > + {openRowIndex2 === index ? : } + + + )} + + + + {/* ============================================ || collapsive row 1 (view) || ============================================ */} + {openRowIndex1 === index && ( + + + + + + Details + + } + > - + {row.tenantname} - Tenant + Client @@ -1106,33 +680,10 @@ const Clients1 = () => { - + - {row.firstname} - - Contact Person - - - - - - - - - - - {row.primarycontact} + {row.phone} Phone @@ -1142,84 +693,20 @@ const Clients1 = () => { - + - {row.primaryemail} + {row.email} E-Mail - + - - - - {row.address} - - Address - - - - - - {/* - - - - - - {row.subcategoryname} - - Category - - - - - - - - {row.suburb} - - - Location - - - - */} - - - - - + {row.city} @@ -1227,624 +714,116 @@ const Clients1 = () => { City - - - - - - {row.postcode} - - - PostCode - - - - - - - - - - - {row.latitude} - - Latitude - - - - - - - - {row.longitude} - - - Longitude - - - )} - {/* ============================================= || Pricing || ============================================= */} - {value1 == 1 && ( - - - - - - # - Date - Slab - Base Price - Min Kms - Price/Km - Other Charges - - - - {clientpricelist?.length === 0 ? ( - - - - - - ) : ( - clientpricelist.map((val, i) => ( - - {i + 1} - {dayjs(val.pricingdate).format('DD-MM-YYYY')} - {val.slab} - {val.baseprice} - {val.minkm} - {val.priceperkm} - {val.othercharges} - - )) - )} - -
-
-
- )} -
-
-
-
- )} - {/* ============================================ || collapsive row (2) (edit)|| ============================================ */} - {openRowIndex2 === index && ( - - - {/* */} - - - - } - sx={{ - alignItems: 'center', - flexDirection: 'row' - }} - /> - {value0 !== 1 && ( - - } - sx={{ - alignItems: 'center', - flexDirection: 'row' - }} - onClick={() => { - getAppPricing(selectedCustomer.applolcationid); - }} - /> - )} - - - {value2 == 1 && ( - - )} - - } - > - {/* ============================================= || Edit Details || ============================================= */} - {value2 == 0 && ( - - - {/* =========================|| Edit Details (right) || =========================*/} - - - - - {/* =========================|| Tenant || =========================*/} - - - - - - - { - setEditClient({ - ...editClient, - tenantname: e.target.value - }); - }} - sx={{ mt: 2 }} - /> - - - {' '} - {/* =========================|| Contact Person || =========================*/} - - - - - - - { - setEditClient({ - ...editClient, - firstname: e.target.value - }); - }} - sx={{ mt: 2 }} - /> - - - - {/* =========================|| Contact Number || =========================*/} - - - - - - - { - setEditClient({ - ...editClient, - primarycontact: e.target.value - }); - }} - sx={{ mt: 2 }} - /> - - - - {/* =========================|| E-Mail || =========================*/} - - - - - - - { - setEditClient({ - ...editClient, - primaryemail: e.target.value - }); - }} - sx={{ mt: 2 }} - /> - - - - {/* =========================|| Address || =========================*/} - - - - - - - (typeof option === 'string' ? option : option.description)} - filterOptions={(x) => x} - options={options} - autoComplete - includeInputInList - filterSelectedOptions - defaultValue={selectedCustomer.address} - noOptionsText="No locations" - onChange={(event, newValue) => { - setOptions(newValue ? [newValue, ...options] : options); - setValue(newValue || ''); - console.log('newValue', newValue); - setAddress(newValue?.description || ''); - setEditClient({ - ...editClient, - address: newValue?.description || '' - }); - }} - onInputChange={(event, newInputValue) => { - setInputValue(newInputValue); - }} - renderInput={(params) => ( - - )} - renderOption={(props, option) => { - const matches = option.structured_formatting.main_text_matched_substrings || []; - - const parts = parse( - option.structured_formatting.main_text, - matches.map((match) => [match.offset, match.offset + match.length]) - ); - - return ( -
  • - - - - - - {parts.map((part, index) => ( - - {part.text} - - ))} - - {option.structured_formatting.secondary_text} - - - -
  • - ); - }} - /> -
    -
    -
    - {/* =========================|| category name || =========================*/} - {/* - - - - - - { - setEditClient({ - ...editClient, - subcategoryname: e.target.value - }); - }} - /> - - - - - - { - setEditClient({ - ...editClient, - suburb: e.target.value - }); - }} - value={selectedCustomer.suburb} - /> - - - */} - {/* =========================|| city || =========================*/} - - - - - - - { - setEditClient({ - ...editClient, - city: e.target.value - }); - }} - value={city == '' ? selectedCustomer.city : city} - /> - - - - - - { - { - setEditClient({ - ...editClient, - postcode: e.target.value - }); - } - }} - value={zipcode == '' ? selectedCustomer.postcode : zipcode} - /> - - - - {/* =========================|| latitude || =========================*/} - - - - - - - - - - - - - - - - +
    +
    +
    +
    + )} + {/* ============================================ || collapsive row 2 (edit)|| ============================================ */} + {openRowIndex2 === index && ( + + + + + Edit Details + + } + > + + + + + + + - - {/* =========================|| update || =========================*/} - - - - - + + setEditClient({ ...editClient, clientname: e.target.value })} + sx={{ mt: 2 }} + /> + + + + + + + + + + setEditClient({ ...editClient, phone: e.target.value })} + sx={{ mt: 2 }} + /> + + + + + + + + + + setEditClient({ ...editClient, email: e.target.value })} + sx={{ mt: 2 }} + /> + + + + + + + + + + setEditClient({ ...editClient, city: e.target.value })} + sx={{ mt: 2 }} + /> + + + - + + + + - )} - {/* ============================================= || Edit Pricing || ============================================= */} - {value2 == 1 && ( - - - - - - # - Date - Slab - Base Price - Min Kms - Price/Km - Other Charges - - - - {clientpricelist?.length === 0 ? ( - - - - - - ) : ( - clientpricelist.map((val, i) => ( - - {i + 1} - {dayjs(val.pricingdate).format('DD-MM-YYYY')} - {val.slab} - {val.baseprice} - {val.minkm} - {val.priceperkm} - {val.othercharges} - - )) - )} - -
    -
    -
    - )} -
    - {/* */} -
    -
    - )} - + + + + )} + ); }) )} @@ -1868,10 +847,10 @@ const Clients1 = () => { - No tenants to show + No clients to show - {`No ${activeTabMeta.label.toLowerCase()} tenants for this filter.`} + {`No ${activeTabMeta.label.toLowerCase()} clients for this filter.`} ) : ( @@ -1914,9 +893,7 @@ const Clients1 = () => { - - {rowStatusMeta.label} - + {rowStatusMeta.label} @@ -1933,36 +910,13 @@ const Clients1 = () => { onClick={() => { setSelectedCustomer(row); setSelectedtenid(row.tenantid); - setAppId(row.applocationid); - setTimeout(() => { - tenantupdate(row.tenantid); - }, 100); + tenantupdate(row.tenantid); }} > )} - {value0 == 1 && ( - { - setSelectedCustomer(row); - setDialogopen(true); - setSelectedtenid(row.tenantid); - setAppId(row.applocationid); - getAppPricing(row.applolcationid); - }} - > - - - )} - {value0 == 2 && ( + {(value0 == 1 || value0 == 2) && ( { onClick={() => { setSelectedCustomer(row); setSelectedtenid(row.tenantid); - setAppId(row.applocationid); - setTimeout(() => { - tenantupdate(row.tenantid); - }, 100); + tenantupdate(row.tenantid); }} > - + {value0 == 1 ? : } )} { }} onClick={() => { setSelectedCustomer(row); + setEditClient({ clientname: row.tenantname, email: row.email, phone: row.phone, city: row.city }); setSelectedtenid(row.tenantid); - setAppId(row.applocationid); - getAppPricing(row.applolcationid); - setDialogopen(true); + handleCollapseToggle2(index); + setOpenRowIndex1(-1); }} > @@ -2025,24 +976,16 @@ const Clients1 = () => { } > - - - - - - {row.address || '—'} - - + + + + {expanded && ( - - - - - + )} @@ -2069,169 +1012,6 @@ const Clients1 = () => { )} - {/* // ==============================||( Client Pricing ) dialog (dialogopen) ||============================== // */} - - - {`Client Pricing - (${selectedCustomer.tenantname})`} - - - - - - Select Slab - `${option.slab}`} - fullWidth - selectOnFocus - renderInput={(params) => } - onChange={(event, value, reason) => { - setSelectedPricing(value); - console.log('pricing', value); - setIsprice(false); - - if (reason === 'clear') { - setIsprice(true); - } - }} - /> - - - - Base Price - { - setSelectedPricing({ - ...selectedPricing, - baseprice: e.target.value - }); - }} - /> - - - Price/Km - { - setSelectedPricing({ - ...selectedPricing, - priceperkm: e.target.value - }); - }} - /> - - - - Min Kms - { - setSelectedPricing({ - ...selectedPricing, - minkm: e.target.value - }); - }} - /> - - - Max Kms - { - setSelectedPricing({ - ...selectedPricing, - maxkm: e.target.value - }); - }} - /> - - - Min Orders - { - setSelectedPricing({ - ...selectedPricing, - minorder: e.target.value - }); - }} - /> - - - - - - - - - {value2 == 1 && ( - - )} - {value0 == 1 && ( - - )} - - - - - - ); }; diff --git a/src/pages/nearle/dispatch/Dispatch.js b/src/pages/nearle/dispatch/Dispatch.js index 17f0ddb..df0f0c6 100644 --- a/src/pages/nearle/dispatch/Dispatch.js +++ b/src/pages/nearle/dispatch/Dispatch.js @@ -1079,7 +1079,7 @@ const Dispatch = ({ // • no batch is selected, // • the previous request is still in flight (prevents queue // stacking on slow networks), - // • the browser tab is hidden (saves API quota on routes.workolik). + // • the browser tab is hidden (saves API quota on the Doormile AI layer). // Loading state is tracked through a ref so the interval doesn't // reset on every in-flight flip. ───────────────────────────────── const ANALYSIS_POLL_MS = 15000; diff --git a/src/pages/nearle/login.js b/src/pages/nearle/login.js index 19eeb5b..24386d1 100644 --- a/src/pages/nearle/login.js +++ b/src/pages/nearle/login.js @@ -1,3 +1,5 @@ +// UNUSED — login1.js is the active login page (see routes/MainRoutes.js). This +// file still targets the old jupiter.nearle.app console-login flow. import { useState, useEffect } from 'react'; import { enqueueSnackbar } from 'notistack'; import AnimateButton from 'components/@extended/AnimateButton'; diff --git a/src/pages/nearle/login1.js b/src/pages/nearle/login1.js index b4f1134..0f3cc4e 100644 --- a/src/pages/nearle/login1.js +++ b/src/pages/nearle/login1.js @@ -28,17 +28,17 @@ const DOORMILE_RED_FILTER = 'brightness(0) saturate(100%) invert(15%) sepia(93%) import axios from 'axios'; import { useNavigate } from 'react-router-dom'; // import { openSnackbar } from 'store/reducers/snackbar'; -// import { useDispatch } from 'react-redux'; +import { useSelector } from 'react-redux'; import Loader from 'components/Loader'; import { enqueueSnackbar } from 'notistack'; const Login = () => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const fcmtoken = useSelector((state) => state.fcm); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [, setAlertmessage] = useState(''); - const [checkusername, setCheckusername] = useState(false); // const [toast, setToast] = useState(false); const [loading, setLoading] = useState(false); let navigate = useNavigate(); @@ -76,116 +76,48 @@ const Login = () => { // console.log(alertmessage) }, []); - const usernamecheck = async (e) => { - e.preventDefault(); - setUsername(e.target.value); - if (e.target.value) { - try { - // await axios.post(`${process.env.REACT_APP_URL}/auth/login`, { - // "authname": e.target.value - // }) - await axios - .post(`${process.env.REACT_APP_URL}/users/login`, { - authname: e.target.value, - configid: 1 - // "contactno": e.target.value, - // "password": 'admin' - }) - .then((res) => { - console.log(res.data); - if (res.data.details.authname === e.target.value) { - setUsername(e.target.value); - setCheckusername(false); - } else { - setCheckusername(true); - } - // if (res.data.authname === e.target.value) { - - // setUsername(e.target.value); - // setCheckusername(false); - // } - }) - .catch(() => { - setCheckusername(true); - }); - } catch (err) { - console.log(err); - } - } - }; - const loginsend = async () => { - // e.preventDefault(); setLoading(true); - if (password && username) { - if (password == 'admin') { - setSubmitting(true); - try { - await axios - .post(`${process.env.REACT_APP_URL}/users/partner/login`, { - // "authname": username, - configid: 1, - contactno: username - - // "password": password - }) - .then((res) => { - console.log(res.data); - if (res.data.status) { - if (res.data.details.contactno === username) { - enqueueSnackbar('login Successfull', { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 3000 - }); - setUsername(''); - setPassword(''); - localStorage.setItem('firstname', res.data.details.tenantname); - localStorage.setItem('authname', res.data.details.authname); - - // localStorage.setItem("appuserid", res.data.details.userid); - localStorage.setItem('roleid', res.data.details.roleid); - localStorage.setItem('tenantid', res.data.details.tenantid); - localStorage.setItem('partnerid', res.data.details.partnerid); - - navigate('/orders'); - setSubmitting(false); - } - } - console.log(res.data.message); - setLoading(false); - }) - .catch((err) => { - console.log(err); - - // setAlertmessage('Invalid Data'); - // if(err.message == 'Network Error'){ - opentoast(err.message); - // }else{ - // opentoast('Invalid Data'); - - // } - setLoading(false); - setSubmitting(false); - console.log(err.message); - }); - } catch (err) { - console.log(err); - setLoading(false); - setSubmitting(false); - } - } else { - opentoast('Password is Incorrect'); - setLoading(false); - } - } else { - // let el2 = document.getElementById('toastid'); - // el2.classList.add('d-block'); - // el2.classList.remove('d-none'); + if (!password || !username) { setAlertmessage('Fill All required fields'); opentoast('Fill All required fields'); setLoading(false); + return; + } + + setSubmitting(true); + try { + const res = await axios.post(`${process.env.REACT_APP_URL}/admin/login`, { + email: username, + password, + userfcmtoken: fcmtoken?.token + }); + + if (res.data.success) { + const { token, data } = res.data; + enqueueSnackbar('Login Successful', { + variant: 'success', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 3000 + }); + setUsername(''); + setPassword(''); + localStorage.setItem('authname', data.email || data.firstname); + localStorage.setItem('userid', data.userid); + localStorage.setItem('roleid', data.roleid); + localStorage.setItem('token', token); + axios.defaults.headers.common.Authorization = `Bearer ${token}`; + + navigate('/orders'); + } else { + opentoast(res.data.message || 'Invalid Data'); + } + } catch (err) { + opentoast(err.response?.data?.message || err.message); + } finally { + setLoading(false); + setSubmitting(false); } }; @@ -316,8 +248,8 @@ const Login = () => { variant="outlined" autoComplete="email" required - onChange={usernamecheck} - error={checkusername} + value={username} + onChange={(e) => setUsername(e.target.value)} /> { - // let dispatch = useDispatch(); - - function descendingComparator(a, b, orderBy) { - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - return 0; - } - - function getComparator(order, orderBy) { - return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy); - } - - function stableSort(array, comparator) { - const stabilizedThis = array.map((el, index) => [el, index]); - stabilizedThis.sort((a, b) => { - const order = comparator(a[0], b[0]); - if (order !== 0) { - return order; - } - return a[1] - b[1]; - }); - return stabilizedThis.map((el) => el[0]); - } - - const headCells = [ - { - id: 'sno', - disablePadding: true, - label: '#' - }, - { - id: 'tenantname', - numeric: false, - disablePadding: false, - label: 'REQUESTOR' - }, - { - id: 'contact', - numeric: false, - disablePadding: false, - label: 'BANK' - }, - { - id: 'address3', - disablePadding: false, - label: 'IFSC' - }, - { - id: 'address', - disablePadding: false, - label: 'REF NO' - }, - { - id: 'amount', - disablePadding: false, - label: 'AMOUNT' - }, - { - id: 'city', - disablePadding: false, - label: 'REASON' - } - // { - // id: 'action', - // disablePadding: false, - // label: 'ACTION', - // } - ]; - - function EnhancedTableHead(props) { - const { order, orderBy, onRequestSort } = props; - const createSortHandler = (property) => (event) => { - onRequestSort(event, property); - }; - - return ( - - - {headCells.map((headCell) => ( - - - {headCell.label} - {orderBy === headCell.id ? ( - - {order === 'desc' ? 'sorted descending' : 'sorted ascending'} - - ) : null} - - - ))} - - - ); - } - - EnhancedTableHead.propTypes = { - numSelected: PropTypes.number.isRequired, - onRequestSort: PropTypes.func.isRequired, - onSelectAllClick: PropTypes.func.isRequired, - order: PropTypes.oneOf(['asc', 'desc']).isRequired, - orderBy: PropTypes.string.isRequired, - rowCount: PropTypes.number.isRequired - }; - - function EnhancedTable() { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); - const [order, setOrder] = React.useState('asc'); - const [orderBy, setOrderBy] = React.useState('calories'); - const [selected, setSelected] = React.useState([]); - const [page, setPage] = React.useState(0); - const [rowsPerPage, setRowsPerPage] = React.useState(10); - - const [clientname, setClientname] = useState(''); - const [emailaddress, setEmailaddress] = useState(''); - const [mobilenumber, setMobilenumber] = useState(''); - const [regno, setRegno] = useState(''); - const [address, setAddress] = useState(''); - const [city, setCity] = useState(''); - const [zipcode, setZipcode] = useState(''); - const [contactname, setContactname] = useState(''); - const [state1, setState1] = useState(''); - const [suburb, setSuburb] = useState(''); - const [currenttenantid] = useState(''); - const [latlong, setLatlong] = useState({}); - Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY); - // const [alertmessage, setAlertmessage] = useState(''); - // const [toast, setToast] = useState(false); - const [rolesarr, setRolesarr] = useState([]); - const [roleslist] = useState([]); - const [rolestab, setRolestab] = useState(0); - - const [approveid] = useState(false); - const [disableid] = useState(false); - const [loading1] = useState(false); - - const [refno, setRefno] = useState(''); - const [requestor, setRequestor] = useState(''); - const [bankname, setBankname] = useState(''); - - useEffect(() => { - setRolesarr([ - { - sno: 1, - role: '', - cost: '', - serviceid: 0, - tenantid: 0, - categoryid: 0, - subcategoryid: 0, - servicecode: '', - servicename: '', - unitid: 0, - unitname: '', - serviceamount: '', - discountid: 0, - taxpercent: 0, - taxamount: 0, - servicevalue: 0, - categoryname: '' - } - ]); - console.log(rolesarr); - // fetchroleslist(); - }, []); - - const opentoast = (message) => { - enqueueSnackbar(message, { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - console.log(alertmessage); - }; - - // const opentoast = () => { - // setToast(true) - - // setTimeout(() => { - // setToast(false) - // }, 2000); - - // } - - useEffect(() => { - try { - Geocode.fromAddress(address).then( - (response) => { - if (response.status == 'OK') { - const { lat, lng } = response.results[0].geometry.location; - setLatlong({ - lat, - lng - }); - console.log(response); - } - }, - (error) => { - console.log(error); - } - ); - } catch (err) { - console.log(err); - } - }, [address]); - - useEffect(() => { - console.log('rolesarr'); - console.log(rolesarr); - }, [rolesarr]); - - - - const addarr = () => { - let arr = rolesarr; - if (arr[arr.length - 1].role && arr[arr.length - 1].cost) { - arr.push({ - sno: arr.length + 1, - cost: '', - role: '', - serviceid: 0, - tenantid: 0, - categoryid: 0, - subcategoryid: 0, - servicecode: '', - servicename: '', - unitid: 0, - unitname: '', - serviceamount: '', - discountid: 0, - taxpercent: 0, - taxamount: 0, - servicevalue: 0, - categoryname: '' - }); - setRolesarr([...arr]); - } else { - // setAlertmessage('Fill all Previous Details'); - opentoast('Fill all Previous Details'); - } - }; - const deletearr = async (sno, val1) => { - console.log(val1); - let arr = rolesarr; - - if (val1.serviceid !== 0 && rolesarr.length > 1) { - console.log([ - { - serviceid: val1.serviceid, - tenantid: val1.tenantid, - categoryid: val1.categoryid, - subcategoryid: val1.subcategoryid, - servicecode: val1.servicecode, - servicename: val1.servicename, - unitid: val1.unitid, - unitname: val1.unitname, - serviceamount: val1.servicevalue - } - ]); - try { - await axios - .delete(`${process.env.REACT_APP_URL2}/tenants/delete/services`, { - data: [ - { - serviceid: val1.serviceid, - tenantid: val1.tenantid, - categoryid: val1.categoryid, - subcategoryid: val1.subcategoryid, - servicecode: val1.servicecode, - servicename: val1.servicename, - unitid: val1.unitid, - unitname: val1.unitname, - serviceamount: val1.servicevalue - } - ] - }) - .then((res) => { - console.log('res'); - console.log(res); - if (res.data.message === 'Deleted successful') { - enqueueSnackbar('Client pricing Deleted', { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - arr.splice(sno - 1, 1); - arr.map((val, i) => { - val.sno = i + 1; - }); - console.log(arr); - setRolesarr([...arr]); - } - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - } catch (err) { - console.log(err); - setLoading(false); - } - } else if (rolesarr.length > 1) { - arr.splice(sno - 1, 1); - arr.map((val, i) => { - val.sno = i + 1; - }); - console.log(arr); - setRolesarr([...arr]); - } else if (rolesarr.length === 1) { - setRolesarr([ - { - sno: 1, - role: '', - cost: '', - serviceid: val1.serviceid, - tenantid: 0, - categoryid: 0, - subcategoryid: 0, - servicecode: '', - servicename: '', - unitid: 0, - unitname: '', - serviceamount: '', - discountid: 0, - taxpercent: 0, - taxamount: 0, - servicevalue: 0, - categoryname: '' - } - ]); - } - }; - - - - - - const clientupdate = async () => { - if (!clientname) { - // setAlertmessage('Fill Business name'); - opentoast('Fill Business name'); - } else if (!regno) { - // setAlertmessage('Fill Business No.'); - opentoast('Fill Business No.'); - } else if (!emailaddress) { - // setAlertmessage('Fill Email address'); - opentoast('Fill Email address'); - } else if (!mobilenumber) { - // setAlertmessage('Fill Mobile number'); - opentoast('Fill Mobile number'); - } else if (!contactname) { - // setAlertmessage('Fill Contact name'); - opentoast('Fill Contact name'); - } else if (!address) { - // setAlertmessage('Fill Address'); - opentoast('Fill Address'); - } else if (!city) { - // setAlertmessage('Fill City name'); - opentoast('Fill City name'); - } else if (!zipcode) { - // setAlertmessage('Fill Zip code'); - opentoast('Fill Zip code'); - } else if (!latlong.lat || !latlong.lng) { - setAlertmessage('Fill correct address'); - opentoast('Fill correct address'); - } else if ((approveid || disableid) && !(rolesarr[0].role && rolesarr[0].cost)) { - opentoast('Fill client pricing'); - } else { - let obj = { - tenantid: currenttenantid, - registrationno: regno, - tenantname: clientname, - primaryemail: emailaddress, - primarycontact: contactname, - contactno: mobilenumber, - address: address, - suburb: suburb, - city: city, - state: state1, - postcode: zipcode, - latitude: latlong.lat.toString(), - longitude: latlong.lng.toString(), - approved: (approveid && tabvalue === 1) || (!approveid && tabvalue === 0 && !disableid) ? 1 : 0 - }; - console.log(obj); - - try { - setLoading(true); - await axios - .put(`${process.env.REACT_APP_URL2}/tenants/update`, obj) - .then((res) => { - console.log('res:', res); - if (res.data.message === 'Update successful') { - enqueueSnackbar('Client Details Updated Successfully', { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 3000 - }); - setLoading(true); - - setTimeout(() => { - clientdetailspending(); - clientdetailsapproved(); - setTabvalue(0); - setLoading(false); - }, 2000); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - } catch (err) { - console.log(err); - setLoading(false); - } - } - }; - - const rolepricesubmit = async () => { - console.log('submit'); - let arr = []; - let objcheck = false; - rolesarr.map((val) => { - if (!val.role || !val.cost) { - objcheck = true; - } - arr.push({ - serviceid: val.serviceid, - tenantid: currenttenantid, - categoryid: val.categoryid, - subcategoryid: val.subcategoryid, - servicecode: val.servicecode, - servicename: val.servicename, - unitid: val.unitid, - unitname: val.unitname, - serviceamount: parseFloat(val.serviceamount), - categoryname: val.categoryname, - subcategoryname: val.servicename - // discountid: val.discountid, - // taxpercent: val.taxpercent, - // taxamount: val.taxamount, - // servicevalue: parseFloat(val.servicevalue), - }); - }); - console.log(arr); - if (!objcheck) { - try { - setLoading(true); - // await axios.post(`${process.env.REACT_APP_URL2}/tenants/createservice`, arr)tenants/update/services - await axios - .put(`${process.env.REACT_APP_URL2}/tenants/update/services`, arr) - .then((res) => { - console.log('res:', res); - if (res.data.message === 'Update successful') { - enqueueSnackbar('Service Updated Successfully', { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - } catch (err) { - console.log(err); - setLoading(false); - } - } else { - enqueueSnackbar('Fill all Details', { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - } - }; - - const createrequest = () => { - if (!refno) { - // setAlertmessage('Fill Business name'); - opentoast('Fill Reference No'); - } else if (!requestor) { - // setAlertmessage('Fill Business No.'); - opentoast('Fill Requestor'); - } else if (!bankname) { - // setAlertmessage('Fill Email address'); - opentoast('Fill Bank Name'); - } else if (!amount) { - // setAlertmessage('Fill Mobile number'); - opentoast('Fill Amount'); - } else if (!accountno) { - // setAlertmessage('Fill Contact name'); - opentoast('Fill Account No'); - } else if (!ifsc) { - // setAlertmessage('Fill Address'); - opentoast('Fill IFSC'); - } else if (!reason) { - // setAlertmessage('Fill City name'); - opentoast('Fill Reason'); - } else { - let obj = { - requestid: 0, - requestdate: dayjs().format('YYYY-MM-DD HH:mm:ss'), - referenceno: refno, - Apptypeid: 22, - requesttype: 'staffexpenses', - reason: reason, - requestor: requestor, - amount: amount, - Accountno: accountno, - bankname: bankname, - ifsccode: ifsc - }; - - console.log(obj); - } - }; - - const handleRequestSort = (event, property) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; - - const handleSelectAllClick = (event) => { - if (event.target.checked) { - const newSelected = rows.map((n) => n.name); - setSelected(newSelected); - return; - } - setSelected([]); - }; - - const handleClick = (event, name) => { - const selectedIndex = selected.indexOf(name); - let newSelected = []; - - if (selectedIndex === -1) { - newSelected = newSelected.concat(selected, name); - } else if (selectedIndex === 0) { - newSelected = newSelected.concat(selected.slice(1)); - } else if (selectedIndex === selected.length - 1) { - newSelected = newSelected.concat(selected.slice(0, -1)); - } else if (selectedIndex > 0) { - newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)); - } - - setSelected(newSelected); - }; - - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = (event) => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - - // const handleChangeDense = (event) => { - // setDense(event.target.checked); - // }; - - const isSelected = (name) => selected.indexOf(name) !== -1; - - // Avoid a layout jump when reaching the last page with empty rows. - const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0; - - const visibleRows = React.useMemo( - () => stableSort(rows, getComparator(order, orderBy)).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage), - [order, orderBy, page, rowsPerPage] - ); - - return ( - <> - - Create Request - - - - - {/* */} - {/* Client} sx={{ height: '100%' }}> - - */} - - {/* */} - Reference No - { - setRefno(e.target.value); - console.log(e); - }} - /> - - - Requestor - setRequestor(e.target.value)} - // label='Business No.' - fullWidth - sx={{ width: '100%' }} - /> - {/* */} - - - - {/* - */} - - Bank Name - - setBankname(e.target.value)} - // label='Email' - /> - - - Amount - - setAmount(e.target.value)} - /> - - {/* - - */} - - {/* */} - Account No - - { - setAccountno(e.target.value); - console.log(e); - }} - /> - - - IFSC Code - - setIfsc(e.target.value)} - // label='Business No.' - fullWidth - sx={{ width: '100%' }} - /> - {/* */} - - - Reason - - setReason(e.target.value)} - // label='Business No.' - fullWidth - sx={{ width: '100%' }} - /> - {/* */} - - - - - - - - - - - - - - - - - {isMobile && ( - - {loading && - [0, 1, 2, 3, 4].map((item) => ( - - - - - - - - - - } /> - } /> - - - ))} - - {!loading && - visibleRows.map((row) => { - const isItemSelected = isSelected(row.sno); - return ( - handleClick(event, row.sno)} - header={ - - - - {row.requestor ? String(row.requestor).charAt(0).toUpperCase() : '#'} - - - - {row.requestor || '—'} - - - #{row.sno} - - - - {row.amount != null && ( - - )} - - } - > - - - - - - - - - ); - })} - - {!loading && visibleRows.length === 0 && ( - - - No requests to show - Requests will appear here once available. - - )} - - )} - - - - - - {loading && ( - <> - - {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((item) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - - - )} - - - {visibleRows.map((row, index) => { - const isItemSelected = isSelected(row.sno); - const labelId = `enhanced-table-checkbox-${index}`; - return ( - <> - handleClick(event, row.sno)} - role="checkbox" - aria-checked={isItemSelected} - tabIndex={-1} - key={row.sno} - // selected={isItemSelected} - sx={{ cursor: 'pointer' }} - > - {/* - - */} - - {row.sno} - - - - - - - {row.requestor} - {/* - {row.primaryemail} - */} - - - - - {row.accountno} - {row.bankname} - - {row.ifsccode} - - {/* - - {row.address.slice(0, 15)}... - - */} - {row.referenceno} - - {row.amount} - {row.reason} - {/* - - {row.reason} - - */} - - - - - - - - {loading1 ? ( - <> - - {/* - Loading... - */} - - - - - - ) : ( - <> - setRolestab(i)}> - - - - {rolestab === 0 && ( - <> - - - - - - - - - {clientname} - {regno} - - - - {/* - - */} - {/* - - - All orders - - - - - Covered orders - - - - */} - - - - - - - - - - {/* */} - {emailaddress} - {/* */} - - - - - - {/* */} - - {/* - {/* */} - - {/* - - - - - city - - - {/* */} - - - - - - - {/* */} - Contact Details} sx={{ height: '100%' }}> - - - - - - Contact Name - {contactname} - - - - {/* - Contact number - - {mobilenumber} - - */} - - - - - - - Address - {address} - - - - - - - City - {city} - - - - - Zip Code - - {/* */} - {zipcode} - - - - - - - - - {/* */} - - - - )} - {rolestab === 1 && ( - <> - - - - {/* */} - {rolesarr[0].role ? ( - <> - -
    - - - # - Category - - Skill - - Cost/Hr - - {/* */} - - - - {rolesarr.map((val1) => { - return ( - <> - - {val1.sno} - {val1.categoryname} - {val1.role} - {val1.cost} - - - ); - })} - -
    -
    - - ) : ( - <> - No Data found - - )} - {/* - - */} - {/* - - - - - - - - */} - {/* */} - - - - - )} - - )} - - - - - {/* edit page collapse */} - - - - {loading1 ? ( - <> - - {/* - Loading... - */} - - - - - - ) : ( - <> - - setRolestab(i)}> - - - - {rolestab === 0 && ( - <> - - - - Client
    } sx={{ height: '100%' }}> - - - {/* */} - {/* */} - - {/* Client name */} - setClientname(e.target.value)} - /> - - setRegno(e.target.value)} - label="Business No." - // placeholder='Registration Number' - fullWidth - sx={{ width: '100%' }} - /> - - {/* */} - - - - - - } - sx={{ width: '100%' }} - onChange={(e) => setEmailaddress(e.target.value)} - label="Email" - /> - - +1 - }} - sx={{ width: '100%' }} - onChange={(e) => setMobilenumber(e.target.value)} - /> - {/* */} - {/* */} - {/* */} - - - - - - - - {/* */} - Contact Details} sx={{ height: '100%' }}> - {/* - */} - - - - {/* Contact Name */} - setContactname(e.target.value)} - label="Contact Name" - /> - {/* name */} - - - - {/* */} - {/* - */} - {/* */} - - - {/* Address */} - {/* {((row.sno === 1)) && */} - - <> - {/* setAddress(e.target.value)} - inputRef={materialRef} - - - /> */} - - - {/* } */} - - { - setAddress(place.formatted_address); - let city1, state, zipcode1, suburb1; - for (let i = 0; i < place.address_components.length; i++) { - for (let j = 0; j < place.address_components[i].types.length; j++) { - switch (place.address_components[i].types[j]) { - case 'locality': - city1 = place.address_components[i].long_name; - break; - case 'administrative_area_level_1': - state = place.address_components[i].long_name; - break; - case 'postal_code': - zipcode1 = place.address_components[i].long_name; - break; - case 'sublocality': - suburb1 = place.address_components[i].long_name; - break; - } - } - } - setCity(city1 || ''); - setState1(state || ''); - setZipcode(zipcode1 || ''); - setSuburb(suburb1 || ''); - }} - options={{ - types: ['address' || 'geocode'] - }} - placeholder="Address" - value={address} - onChange={(e) => setAddress(e.target.value)} - /> - - - {/* */} - {/* - */} - {/* */} - - - {/* City */} - {/* City */} - setCity(e.target.value)} - /> - {/* {'data.address'} */} - - - - - {/* Zip Code */} - {/* - - */} - setZipcode(e.target.value)} - /> - - - - - - - - - {/* - - */} - - - {/* */} - - - - - )} - {rolestab === 1 && ( - <> - - - - - - - - - # - Category - - Skill - - Cost/Hr - - - - - - {rolesarr.map((val1) => { - return ( - <> - - {val1.sno} - {/* {val1.sno} */} - {val1.categoryname} - - - -b.categoryname.localeCompare(a.categoryname) - )} - groupBy={(option) => option.categoryname} - getOptionLabel={(option) => option.subcategoryname} - isOptionEqualToValue={(option, value) => - option.subcategoryid === value.subcategoryid - } - onChange={(e, val) => { - if (val) { - console.log('eval', e); - // console.log(skillsarr) - // let res = rolesarr.find((val2) => val2.role === val.servicename); - let res = rolesarr.find( - (val2) => val2.subcategoryid === val.subcategoryid - ); - - console.log(val); - if (!res) { - let arr = rolesarr; - arr[val1.sno - 1].role = val.subcategoryname; - arr[val1.sno - 1].categoryname = val.categoryname; - - // arr[val1.sno - 1].Staffroleid = 0; - - arr[val1.sno - 1].categoryid = val.categoryid; - arr[val1.sno - 1].subcategoryid = val.subcategoryid; - // arr[val1.sno - 1].servicename = val.servicename; - arr[val1.sno - 1].servicename = val.subcategoryname; - // arr[val1.sno - 1].servicecode = val.servicecode; - - // arr[val1.sno - 1].unitid = val.serviceunit; - arr[val1.sno - 1].unitid = val.unitid; - // arr[val1.sno - 1].serviceid = val.serviceid; - - // arr[val1.sno - 1].unitname = val.serviceunitname; - arr[val1.sno - 1].unitname = val.unitname; - - // setSkillsarr([...arr]); - setRolesarr([...arr]); - } else { - // setAlertmessage('select different skill') - opentoast('select different skill'); - } - } - console.log(val); - }} - renderInput={(params) => ( - - )} - // renderGroup={(params) => ( - //
  • - - //
    {params.group}
    - //

    {params.children}

    - - //
  • - // )} - // options={roleslist} - value={{ - categoryid: val1.categoryid, - categoryname: '', - cost: val1.serviceamount, - label: val1.servicename, - status: 0, - subcategoryid: val1.subcategoryid, - subcategoryname: val1.servicename, - unitid: val1.unitid, - unitname: val1.unitname - }} - // textContent={val1.servicename} - - disabled={loading ? true : false} - /> -
    - - - { - let arr = rolesarr; - if (e.target.value < 1000) { - arr[val1.sno - 1].cost = e.target.value; - arr[val1.sno - 1].servicevalue = e.target.value; - arr[val1.sno - 1].serviceamount = e.target.value; - - setRolesarr([...arr]); - } - - // forceUpdate() - console.log(e.target.value); - }} - value={val1.cost} - autoComplete="off" - fullWidth - /> - - - - deletearr(val1.sno, val1)} color="error"> - - - -
    - - ); - })} -
    -
    -
    - - - - - - - {/* */} - - - - {/* */} - -
    -
    -
    -
    - - )} - - - )} - - - - - - ); - })} - {emptyRows > 0 && ( - - - - )} - - - - - {/* */} - {/* } - label="Dense padding" - /> */} - {/* */} - - - ); - } - - const outerTheme = useTheme(); - const isMobile = useMediaQuery(outerTheme.breakpoints.down('md')); - const [tabvalue, setTabvalue] = useState(0); - const [rows, setRows] = useState([]); - const [clientapproved, setClientApproved] = useState([]); - const [clientpending, setClientPending] = useState([]); - const [loading, setLoading] = useState(false); - const [searchword, setSearchword] = useState(''); - const [dialogopen, setDialogopen] = useState(false); - - useEffect(() => { - if (localStorage.getItem('partnerid')) { - clientdetailspending(localStorage.getItem('partnerid')); - clientdetailsapproved(localStorage.getItem('partnerid')); - } - }, []); - - const handleChangetab = (e, i) => { - setTabvalue(i); - if (i === 1) setRows(clientapproved); - if (i === 0) setRows(clientpending); - }; - - const clientdetailsapproved = async (tid) => { - setLoading(true); - try { - await axios - .get(`${process.env.REACT_APP_URL}/payments/requests/getpaymentrequest/?partnerid=${tid}&status=1`) - - .then((res) => { - if (res.data.message === 'Successful') { - let arr = []; - res.data.details.map((val, i) => { - arr = [...arr, { ...val, sno: i + 1 }]; - }); - // setArr(arr) - setClientApproved(arr); - console.log(res.data.details); - setLoading(false); - } - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - } catch (err) { - console.log(err); - setLoading(false); - } - }; - - const clientdetailspending = async (tid) => { - setLoading(true); - try { - await axios - .get(`${process.env.REACT_APP_URL}/payments/requests/getpaymentrequest/?partnerid=${tid}&status=0`) - - .then((res) => { - if (res.data.message === 'Success') { - let arr = []; - res.data.details.map((val, i) => { - arr = [...arr, { ...val, sno: i + 1 }]; - }); - // setArr(arr) - setClientPending(arr); - setRows(arr); - console.log(res.data.details); - setLoading(false); - } - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - } catch (err) { - console.log(err); - setLoading(false); - } - }; - - const dialogclose = () => { - setDialogopen(false); - }; - return ( - <> - {loading && } - - - - - Payment Requests - - - - - - {/* */} - - - - } - iconPosition="end" - /> - } - icon={} - /> - - - - - - - } - aria-describedby="header-search-text" - inputProps={{ - 'aria-label': 'weight' - }} - placeholder="Search" - value={searchword} - onChange={(e) => { - setSearchword(e.target.value); - }} - autoComplete="off" - /> - - - {/* */} - - - - - + + + + + + Payment Requests + + Coming soon. This feature isn't available on Doormile yet. + + + ); }; diff --git a/src/pages/nearle/riders/RiderSubstitution.js b/src/pages/nearle/riders/RiderSubstitution.js deleted file mode 100644 index fbe862b..0000000 --- a/src/pages/nearle/riders/RiderSubstitution.js +++ /dev/null @@ -1,671 +0,0 @@ -import React, { useState } from 'react'; -import { - Paper, - Stack, - Typography, - Table, - TableCell, - TableBody, - TableHead, - TableRow, - TableContainer, - Avatar, - Box, - ToggleButtonGroup, - ToggleButton, - Autocomplete, - TextField, - useMediaQuery, - Button -} from '@mui/material'; -import { useTheme } from '@mui/material/styles'; -import { - MdCheckCircle, - MdCancel, - MdAccessTime, - MdInventory2, - MdTwoWheeler, - MdArrowForward -} from 'react-icons/md'; -import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard'; -import dayjs from 'dayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import axios from 'axios'; -import { OpenToast } from 'components/third-party/OpenToast'; - -const STATUS_META = { - active: { label: 'Active', color: '#10b981', icon: MdCheckCircle }, - inactive: { label: 'Inactive', color: '#ef4444', icon: MdCancel }, - online: { label: 'Online', color: '#10b981', icon: MdCheckCircle }, - offline: { label: 'Offline', color: '#ef4444', icon: MdCancel }, - idle: { label: 'Idle', color: '#f59e0b', icon: MdAccessTime }, - unknown: { label: 'Unknown', color: '#94a3b8', icon: MdInventory2 } -}; - -export default function RiderSubstitution({ - appId, - rows, - allRidersList, - substituteRidersList, - substituteAssignments, - handleAssignSubstitute, - onFinalizeSuccess, - selectedDate, - setSelectedDate, - DT, - BRAND, - edge, - tint, - soft -}) { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); - const [selectedBatch, setSelectedBatch] = useState('all'); - const [isFinalizing, setIsFinalizing] = useState(false); - - const getBatchForTime = React.useCallback((timeStr) => { - if (!timeStr) return 'unknown'; - // Use dayjs to parse the time, prepending the date for standard time-only formats - let d; - if (timeStr.includes('-') || timeStr.includes('/')) { - d = dayjs(timeStr); - } else { - d = dayjs(`${dayjs().format('MM-DD-YYYY')} ${timeStr}`); - } - - if (!d.isValid()) { - // Fallback to simple split if dayjs fails - const parts = timeStr.split(':'); - const hr = parseInt(parts[0], 10) || 0; - const min = parseInt(parts[1], 10) || 0; - const decimalHour = hr + min / 60; - if (decimalHour >= 0 && decimalHour < 12) return 'morning'; - if (decimalHour >= 12 && decimalHour < 16) return 'afternoon'; - return 'evening'; - } - - const decimalHour = d.hour() + d.minute() / 60; - if (decimalHour >= 0 && decimalHour < 8.5) { - return 'morning'; - } else if (decimalHour >= 8.5 && decimalHour < 13.5) { - return 'afternoon'; - } else { - return 'evening'; - } - }, []); - - const isBatchDisabled = React.useCallback((batch) => { - if (!selectedDate) return false; - const isToday = selectedDate.isSame(dayjs(), 'day'); - const isPast = selectedDate.isBefore(dayjs(), 'day'); - if (isPast) return true; - if (!isToday) return false; - - const now = dayjs(); - const currentHour = now.hour(); - const currentMinute = now.minute(); - const currentTime = currentHour + currentMinute / 60; - - if (batch === 'morning') { - return currentTime >= 7.0; // After 7:00 AM - } - if (batch === 'afternoon') { - return currentTime >= 9.0; // After 9:00 AM - } - if (batch === 'evening') { - return currentTime >= 16.0; // After 4:00 PM - } - return false; - }, [selectedDate]); - - const filteredRows = React.useMemo(() => { - return rows; - }, [rows]); - - const hasAssignments = Object.values(substituteAssignments || {}).some( - (val) => val !== null && val !== undefined - ); - - const handleFinalize = async () => { - setIsFinalizing(true); - try { - let partnerId = ''; - try { - const rawLocs = localStorage.getItem('applocations'); - if (rawLocs) { - const locs = JSON.parse(rawLocs); - const currentLoc = locs.find((l) => l.applocationid === appId); - if (currentLoc && currentLoc.partnerid) { - partnerId = currentLoc.partnerid; - } else { - const firstValidLoc = locs.find((l) => l.partnerid); - if (firstValidLoc && firstValidLoc.partnerid) { - partnerId = firstValidLoc.partnerid; - } - } - } - } catch (e) { - console.error('Error parsing applocations', e); - } - - if (!partnerId) { - const savedPartnerId = localStorage.getItem('partnerid'); - if (savedPartnerId && savedPartnerId !== 'undefined' && savedPartnerId !== 'null') { - partnerId = savedPartnerId; - } - } - - if (!partnerId || partnerId === '0' || partnerId === 0) { - partnerId = 44; - } - - const tenantId = parseInt(partnerId, 10); - - const substitutions = Object.entries(substituteAssignments || {}) - .filter((entry) => entry[1] !== null && entry[1] !== undefined) - .map(([activeRiderId, subRider]) => { - const absentRiderId = parseInt(activeRiderId, 10); - const absentRider = rows?.find((r) => r.userid === absentRiderId); - return { - sub_date: selectedDate ? selectedDate.format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD'), - absent_rider_id: absentRiderId, - absent_rider_name: absentRider?.username || absentRider?.fullname || `Rider #${absentRiderId}`, - sub_rider_id: parseInt(subRider.userid, 10), - sub_rider_name: subRider.username || subRider.fullname || `Rider #${subRider.userid}`, - reason: "Scheduled", - batch: selectedBatch - }; - }); - - const payload = { - tenant_id: tenantId, - substitutions: substitutions - }; - - const url = `${process.env.REACT_APP_URL}/substitutions`; - const response = await axios.post(url, payload); - - if (response.data && response.data.status) { - OpenToast('Substitutions finalized successfully!', 'success', 2000); - if (onFinalizeSuccess) onFinalizeSuccess(); - } else { - OpenToast(response.data?.message || 'Substitutions saved successfully!', 'success', 2000); - if (onFinalizeSuccess) onFinalizeSuccess(); - } - } catch (err) { - console.error('Finalize error:', err); - // Fallback/simulation - OpenToast('Substitutions saved successfully (offline sync)!', 'success', 2000); - if (onFinalizeSuccess) onFinalizeSuccess(); - } finally { - setIsFinalizing(false); - } - }; - - const getRowStatusMeta = (row) => { - const key = (row?.status || '').toLowerCase() === 'active' ? 'active' : 'inactive'; - return STATUS_META[key] || STATUS_META.unknown; - }; - - const totalCols = 6; - - const AccentAvatar = ({ color, selected, size = 24, children }) => ( - - {children} - - ); - - return ( - <> - {/* Filter Row inside its own Paper */} - - - - - Shift Batch: - - val && setSelectedBatch(val)} - aria-label="batch selection" - size="small" - sx={{ - '& .MuiToggleButton-root': { - borderRadius: '8px', - mx: 0.5, - border: `1px solid ${DT.borderSubtle}`, - color: DT.textSecondary, - fontWeight: 600, - textTransform: 'capitalize', - px: 2, - py: 0.5, - '&.Mui-selected': { - bgcolor: BRAND, - color: '#fff', - '&:hover': { - bgcolor: '#900E1D' - } - } - } - }} - > - All Batches - Morning - Afternoon - Evening - - - - - newValue && setSelectedDate(newValue)} - slotProps={{ - textField: { - size: 'small', - sx: { - width: 180, - '& .MuiOutlinedInput-root': { - borderRadius: '20px', - '& fieldset': { borderColor: DT.borderSubtle }, - '&:hover fieldset': { borderColor: BRAND }, - '&.Mui-focused fieldset': { borderColor: BRAND } - } - } - } - }} - /> - - - - - - {/* Results Table/Cards */} - - - {isMobile ? ( - - {filteredRows?.length === 0 && ( - - - - - - No active riders to show - - - )} - - {filteredRows?.length !== 0 && - filteredRows?.map((row, index) => { - const statusMeta = getRowStatusMeta(row); - const StatusIcon = statusMeta.icon; - const riderProfile = allRidersList?.find((r) => r.userid === row.userid); - const starttime = riderProfile?.starttime || row.starttime; - return ( - - - - - {String(index + 1).padStart(2, '0')} - - - #{row?.userid} - - - - - - - {statusMeta.label} - - - - - - - {(row.fullname || row.username || '?').charAt(0).toUpperCase()} - - - - {row.username || '—'} - - - {row.contactno || '—'} - - - - - } - > - - - option?.userid === value?.userid} - getOptionLabel={(option) => `${option.username || option.fullname || ''} (#${option.userid})`} - value={substituteAssignments[row.userid] || null} - onChange={(event, newValue) => handleAssignSubstitute(row.userid, newValue)} - renderInput={(params) => ( - - )} - /> - - - - ); - })} - - ) : ( - - - - # - ID - Rider - - Substitute Rider - Status - - - - {filteredRows?.length === 0 && ( - - - - - - - - No active riders to show - - - - - )} - - {filteredRows?.length !== 0 && - filteredRows?.map((row, index) => { - const statusMeta = getRowStatusMeta(row); - const StatusIcon = statusMeta.icon; - const riderProfile = allRidersList?.find((r) => r.userid === row.userid); - const starttime = riderProfile?.starttime || row.starttime; - return ( - - - - {String(index + 1).padStart(2, '0')} - - - - - #{row?.userid} - - - - - - {(row.fullname || row.username || '?').charAt(0).toUpperCase()} - - - - {row.username || '—'} - - - {row.contactno || '—'} - - - - - - {/* Substitution Arrow & Autocomplete */} - - - - - option?.userid === value?.userid} - getOptionLabel={(option) => `${option.username || option.fullname || ''} (#${option.userid})`} - value={substituteAssignments[row.userid] || null} - onChange={(event, newValue) => handleAssignSubstitute(row.userid, newValue)} - renderInput={(params) => ( - - )} - /> - - - - - - - - - {statusMeta.label} - - - - - ); - })} - -
    - )} -
    - {hasAssignments && ( - - - - )} -
    - - ); -} diff --git a/src/pages/nearle/riders/createrider.js b/src/pages/nearle/riders/createrider.js index 01d8f1f..d5bba22 100644 --- a/src/pages/nearle/riders/createrider.js +++ b/src/pages/nearle/riders/createrider.js @@ -1,235 +1,78 @@ import { useEffect, useState } from 'react'; // material-ui - import { Box, Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography, useMediaQuery, useTheme } from '@mui/material'; -// third-party -// import { PatternFormat } from 'react-number-format'; - // project import import MainCard from 'components/MainCard'; import axios from 'axios'; -// assets -import { usePlacesWidget } from 'react-google-autocomplete'; import Loader from 'components/Loader'; -import Geocode from 'react-geocode'; import { enqueueSnackbar } from 'notistack'; import { useNavigate } from 'react-router'; +import { fetchAppLocations } from 'pages/api/api'; const Createrider = () => { - const [mobilenumber, setMobilenumber] = useState(''); - const [emailaddress, setEmailaddress] = useState(''); - const [city, setCity] = useState(''); - const [zipcode, setZipcode] = useState(''); - const [address, setAddress] = useState(''); - const [state, setState] = useState(''); - const [suburb, setSuburb] = useState(''); - const [latlong, setLatlong] = useState({}); - const [firstname, setFirstname] = useState(''); - const [doorno, setDoorno] = useState(''); - const [landmark, setLandmark] = useState(''); - const [tenantinfo, setTenantinfo] = useState({}); + const [displayname, setDisplayname] = useState(''); + const [phone, setPhone] = useState(''); + const [hubid, setHubid] = useState(''); + const [hubs, setHubs] = useState([]); const navigate = useNavigate(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); - Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY); - // Geocode.setApiKey('AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8'); - const [loading, setLoading] = useState(false); useEffect(() => { - // fetchprofiledetails(localStorage.getItem('appuserid')); - // fetchprofiledetails(181); - if (localStorage.getItem('tenantid')) { - fetchtenantinfo(localStorage.getItem('tenantid')); - } + fetchAppLocations().then((locations) => { + setHubs((locations || []).filter((l) => l.applocationid !== 0)); + }); }, []); - useEffect(() => { - try { - Geocode.fromAddress(address).then( - (response) => { - if (response.status == 'OK') { - const { lat, lng } = response.results[0].geometry.location; - setLatlong({ - lat, - lng - }); - console.log(response); - } - }, - (error) => { - console.log(error); - } - ); - } catch (err) { - console.log(err); - } - }, [address]); - const opentoast = (message) => { enqueueSnackbar(message, { variant: 'error', anchorOrigin: { vertical: 'top', horizontal: 'right' }, autoHideDuration: 2000 }); - // console.log(alertmessage) }; + const createMiler = async () => { + if (!displayname) { + opentoast('Fill miler name'); + return; + } + if (!phone) { + opentoast('Fill phone number'); + return; + } + if (!hubid) { + opentoast('Choose a hub'); + return; + } - - const fetchtenantinfo = async (tid) => { setLoading(true); - await axios - .get(`${process.env.REACT_APP_URL}/tenants/gettenantinfo/?tenantid=${tid}`) - .then((res) => { - console.log(res); - if (res.data.status) { - setTenantinfo(res.data.details); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); + try { + const res = await axios.post(`${process.env.REACT_APP_URL}/admin/milers`, { + displayname, + phone, + hubid, + availabilitystatus: 'Available' }); - }; - - useEffect(() => { - if (selectedImage) { - setAvatar(URL.createObjectURL(selectedImage)); - } - }, [selectedImage]); - - const { ref: materialRef } = usePlacesWidget({ - apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY, - onPlaceSelected: (place) => { - console.log(place); - - setAddress(place.formatted_address); - let city1, zipcode1, state1, suburb1; - for (let i = 0; i < place.address_components.length; i++) { - for (let j = 0; j < place.address_components[i].types.length; j++) { - switch (place.address_components[i].types[j]) { - case 'locality': - city1 = place.address_components[i].long_name; - break; - case 'administrative_area_level_1': - state1 = place.address_components[i].long_name; - break; - case 'postal_code': - zipcode1 = place.address_components[i].long_name; - break; - case 'sublocality': - suburb1 = place.address_components[i].long_name; - break; - } - } - } - setCity(city1 || ''); - setState(state1 || ''); - setZipcode(zipcode1 || ''); - setSuburb(suburb1 || ''); - - // setAddress(place.formatted_address) - }, - // inputAutocompleteValue: "country", - options: { - // componentRestrictions: 'us', - // types: ["establishment"] - types: ['address' || 'geocode'] - } - }); - - const createprofile = async () => { - console.log('res', businessname, businessno, mobilenumber, emailaddress, address, city, zipcode); - - if (!firstname) { - opentoast('Fill Full name'); - } else if (!mobilenumber) { - opentoast('Fill Mobile Number'); - } else if (!emailaddress) { - opentoast('Fill emailaddress'); - } else if (!address) { - opentoast('Fill Address'); - } else if (!city) { - opentoast('Fill City'); - } else if (!zipcode) { - opentoast('Fill post code'); - } else if (!suburb) { - opentoast('Fill suburb'); - } else if (!latlong.lat || !latlong.lng) { - opentoast('Choose valid address'); - } else { - let obj = { - customerid: 0, - configid: 1, - firstname: firstname, - applocationid: tenantinfo.applolcationid, - profileimage: '', - dialcode: '+91', - contactno: mobilenumber, - devicetype: '', - deviceid: '', - customertoken: '', - address: address, - suburb: suburb, - city: city, - state: state, - postcode: zipcode, - landmark: landmark, - doorno: doorno, - latitude: latlong.lat.toString(), - longitude: latlong.lng.toString(), - tenantid: parseInt(localStorage.getItem('tenantid')), - email: emailaddress - }; - - console.log(obj); - - setLoading(true); - try { - await axios - .post(`${process.env.REACT_APP_URL}/customers/create`, obj) - .then((res) => { - console.log(res); - if (res.data.status) { - enqueueSnackbar(' Created Successfully ', { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - navigate('/clients'); - // setTimeout(()=>{ - // fetchprofiledetails(localStorage.getItem('appuserid')); - - // },2000) - } else if (res.data.message == 'Customer Already available') { - enqueueSnackbar('Customer Already available', { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - - setLoading(false); - enqueueSnackbar(err.message, { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - }); - } catch (err) { - console.log(err); - setLoading(false); + if (res.data?.success) { + enqueueSnackbar('Miler created successfully', { + variant: 'success', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 2000 + }); + navigate('/nearle/riders'); + } else { + opentoast(res.data?.message || 'Failed to create miler.'); } + } catch (err) { + opentoast(err.response?.data?.message || err.message || 'Failed to create miler.'); + } finally { + setLoading(false); } }; @@ -238,205 +81,87 @@ const Createrider = () => { {loading && } - - - Create Rider - - - - - - - - - - Admin Name - setFirstname(e.target.value)} - value={firstname} - autoComplete="off" - /> - - - - - - Phone Number - - + + + Create Miler + + + + + + + + + + Name { }} - onChange={(e) => { - if (e.target.value.toString().length <= 10) { - setMobilenumber(e.target.value); - } - }} - value={mobilenumber} + id="miler-name" + placeholder="Name" + onChange={(e) => setDisplayname(e.target.value)} + value={displayname} autoComplete="off" - // disabled - sx={{ cursor: 'not-allowed' }} /> - + + + + Phone Number + + + { + if (e.target.value.toString().length <= 10) { + setPhone(e.target.value); + } + }} + value={phone} + autoComplete="off" + /> + + + + + + Hub + + + - - - Email Address - setEmailaddress(e.target.value)} - value={emailaddress} - autoComplete="off" - /> - - - - - - Address - setAddress(e.target.value)} - inputRef={materialRef} - /> - - - - - - Suburb - setSuburb(e.target.value)} - value={suburb} - autoComplete="off" - /> - - - - - City - setCity(e.target.value)} - value={city} - autoComplete="off" - /> - - - - - - State - setState(e.target.value)} - value={state} - autoComplete="off" - /> - - - - - Post Code - setZipcode(e.target.value)} - value={zipcode} - autoComplete="off" - /> - - - - - - Door No - setDoorno(e.target.value)} - value={doorno} - autoComplete="off" - /> - - - - - Landmark - setLandmark(e.target.value)} - value={landmark} - autoComplete="off" - /> - - - - + + + + + + + - - - - - - - + ); diff --git a/src/pages/nearle/riders/editRider.js b/src/pages/nearle/riders/editRider.js index 2e5c12f..10b7910 100644 --- a/src/pages/nearle/riders/editRider.js +++ b/src/pages/nearle/riders/editRider.js @@ -3,37 +3,18 @@ import { useEffect, useState } from 'react'; // material-ui import { useLocation, useNavigate } from 'react-router-dom'; -import { - Box, - Button, - Grid, - InputLabel, - MenuItem, - Select, - Stack, - TextField, - Typography, - Autocomplete, - useMediaQuery, - useTheme -} from '@mui/material'; +import { Box, Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography, useMediaQuery, useTheme } from '@mui/material'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -// third-party -// import { PatternFormat } from 'react-number-format'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; - // project import import MainCard from 'components/MainCard'; import axios from 'axios'; -import { usePlacesWidget } from 'react-google-autocomplete'; import Loader from 'components/Loader'; -import Geocode from 'react-geocode'; import { enqueueSnackbar } from 'notistack'; -import dayjs from 'dayjs'; import CircularLoader from 'components/CircularLoader'; +import { fetchAppLocations } from 'pages/api/api'; + +const AVAILABILITY_OPTIONS = ['Available', 'Assigned', 'On_Break', 'Offline']; const EditRider = () => { const theme = useTheme(); @@ -41,277 +22,64 @@ const EditRider = () => { const location = useLocation(); const [riderdata, setRiderdata] = useState(null); const navigate = useNavigate(); - const [address, setAddress] = useState(''); - const [suburb, setSuburb] = useState(''); - const [city, setCity] = useState(''); - const [state, setState] = useState(''); - - const [partner, setPartner] = useState({}); - const [vehiclelist, setVehiclelist] = useState([]); - const [accountlist, setAccountlist] = useState([]); - const [partnerlist, setPartnerlist] = useState([]); - - const [shiftlist, setShiftlist] = useState([]); - const [locaName, setLocoName] = useState(); - const userid = localStorage.getItem('userid'); - - Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY); + const [hubs, setHubs] = useState([]); const [loading, setLoading] = useState(false); const fetchRiderData = async (id) => { try { - let riderdataresponse = await axios.get(`https://jupiter.nearle.app/live/api/v1/partners/getriderdetail/?userid=${id}`); - console.log('riderdataresponse', riderdataresponse.data.details); - setRiderdata(riderdataresponse.data.details); - fetchridershifts(riderdataresponse.data.details.applocationid); + // Doormile /admin/milers/:id returns { userid, displayname, phone, + // availabilitystatus, rating, hubid, currentlat, currentlon, + // completedorders, cancelledorders } — this form only edits the + // fields Doormile's PATCH endpoint actually accepts. + const riderdataresponse = await axios.get(`${process.env.REACT_APP_URL}/admin/milers/${id}`); + setRiderdata(riderdataresponse.data.data); } catch (error) { - console.log('fetchmanagerList', error); + console.log('fetchRiderData', error); } }; + useEffect(() => { fetchRiderData(location.state.riderdata.userid); + fetchAppLocations().then((locations) => { + setHubs((locations || []).filter((l) => l.applocationid !== 0)); + }); }, []); - useEffect(() => { - console.log('riderdata', riderdata); - }, [riderdata, address]); - - useEffect(() => { - setAddress(location.state.riderdata.address); - setSuburb(location.state.riderdata.suburb); - setCity(location.state.riderdata.city); - setState(location.state.riderdata.state); - }, []); - - useEffect(() => { - if (localStorage.getItem('tenantid')) { - fetchtenantinfo(localStorage.getItem('tenantid')); - } - fetchvehicle(); - fetchaccounttype(); - }, []); - - useEffect(() => { - if (partner.applocationid) { - fetchridershifts(partner.applocationid); - } - }, [partner.applocationid]); - - - - const fetchtenantinfo = async (tid) => { - setLoading(true); - await axios - .get(`${process.env.REACT_APP_URL}/tenants/gettenantinfo/?tenantid=${tid}`) - .then((res) => { - console.log(res); - if (res.data.status) { - setTenantinfo(res.data.details); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - }; - - const fetchvehicle = async () => { - setLoading(true); - await axios - .get(`${process.env.REACT_APP_URL}/utils/getapptypes?tag=vehicle`) - .then((res) => { - console.log('fetchvehicle', res); - let arr = []; - res.data.map((val) => { - arr.push({ - ...val, - label: val.typename - }); - }); - setVehiclelist([...arr]); - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - }; - - const fetchaccounttype = async () => { - setLoading(true); - await axios - .get(`${process.env.REACT_APP_URL}/utils/getapptypes?tag=accounttype`) - .then((res) => { - console.log(res); - // if (res.data.status) { - let arr = []; - res.data.map((val) => { - arr.push({ - ...val, - label: val.typename - }); - }); - setAccountlist([...arr]); - console.log(arr); - // } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - }; - - // ==============================|| fetchAppLocations ||============================== // - const fetchAppLocations = async () => { - try { - const locationRes = await axios.get(`${process.env.REACT_APP_URL}/partners/getlocations/?userid=${userid}`); - console.log('fetchAppLocations', locationRes.data.details); - setPartnerlist(locationRes.data.details); - } catch (err) { - console.log('locationRes', err); - } - }; - useEffect(() => { - fetchAppLocations(); - }, []); - - const fetchridershifts = async (id) => { - setLoading(true); - await axios - .get(`${process.env.REACT_APP_URL}/partners/getridershifts/?applocationid=${id}`) - .then((res) => { - console.log('fetchridershifts', res); - // if (res.data.status) { - let arr = []; - res.data.details.map((val) => { - arr.push({ - ...val, - label: val.shiftname - }); - }); - setShiftlist([...arr]); - console.log(arr); - // } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - }; - - useEffect(() => { - if (selectedImage) { - setAvatar(URL.createObjectURL(selectedImage)); - } - }, [selectedImage]); - - const { ref: materialRef } = usePlacesWidget({ - apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY, - onPlaceSelected: (place) => { - console.log(place); - setAddress(place.formatted_address); - - let city1, zipcode1, state1, suburb1; - for (let i = 0; i < place.address_components.length; i++) { - for (let j = 0; j < place.address_components[i].types.length; j++) { - switch (place.address_components[i].types[j]) { - case 'locality': - city1 = place.address_components[i].long_name; - break; - case 'administrative_area_level_1': - state1 = place.address_components[i].long_name; - break; - case 'postal_code': - zipcode1 = place.address_components[i].long_name; - break; - case 'sublocality': - suburb1 = place.address_components[i].long_name; - break; - } - } - } - setCity(city1 || ''); - - setState(state1 || ''); - setZipcode(zipcode1 || ''); - setSuburb(suburb1 || ''); - - // setAddress(place.formatted_address) - }, - // inputAutocompleteValue: "country", - options: { - // componentRestrictions: 'us', - // types: ["establishment"] - types: ['address' || 'geocode'] - } - }); - const updateRider = async () => { setLoading(true); - console.log('updated riderData', riderdata); - await axios - .put(`https://jupiter.nearle.app/live/api/v1/partners/updaterider`, { - userid: riderdata.userid, - contactno: riderdata.contactno, - firstname: riderdata.firstname, - lastname: riderdata.lastname, - email: riderdata.email, - address: riderdata.address, - suburb: riderdata.suburb, - city: riderdata.city, - state: riderdata.state, - partnerid: riderdata.partnerid, - applocationid: riderdata.applocationid, - ridersettings: { - riderid: riderdata.riderid, - userid: riderdata.userid, - partnerid: riderdata.partnerid, - shiftid: riderdata.shiftid, - identificationno: riderdata.identificationno, - basefare: riderdata.basefare, - additionalkm: riderdata.additionalkm, - othercharges: riderdata.othercharges, - accountno: riderdata.accountno, - accountname: riderdata.accountname, - accounttypeid: riderdata.accounttypeid, - accounttype: riderdata.accounttype, - bankname: riderdata.bankname, - ifsccode: riderdata.ifsccode, - Branch: riderdata.branch, - vehicleid: riderdata.vehicleid, - vehiclename: riderdata.vehiclename, - vehicleno: riderdata.vehicleno, - model: riderdata.model, - color: riderdata.color, - licenseno: riderdata.licenseno, - insurancedate: dayjs(riderdata.insurancedate).format('YYYY-MM-DD HH:mm:ss') - } - }) - .then((response) => { - console.log('post response', response); - if (response.status == 200) { - enqueueSnackbar(`Updated Sucessfully`, { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - setRiderdata(null); - navigate('/nearle/riders'); - setLoading(false); - } else { - enqueueSnackbar('Update Failed', { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - setLoading(false); - } + try { + const response = await axios.patch(`${process.env.REACT_APP_URL}/admin/milers/${riderdata.userid}`, { + displayname: riderdata.displayname, + phone: riderdata.phone, + availabilitystatus: riderdata.availabilitystatus, + hubid: riderdata.hubid }); + if (response.status == 200) { + enqueueSnackbar('Updated Successfully', { + variant: 'success', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 2000 + }); + navigate('/nearle/riders'); + } else { + enqueueSnackbar('Update Failed', { + variant: 'error', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 2000 + }); + } + } catch (err) { + enqueueSnackbar(err.response?.data?.message || err.message || 'Update Failed', { + variant: 'error', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 2000 + }); + } finally { + setLoading(false); + } }; + return ( <> {loading && ( @@ -329,723 +97,94 @@ const EditRider = () => { spacing={{ xs: 1.5, sm: 0 }} sx={{ backgroundColor: 'secondary.lighter', width: '100%', height: '100%', p: 2 }} > - Edit Rider - } > - - - {/* || =========================================== || Contact Information || =========================================== || */} - - Contact Information - - } - > - - {/* ========================== || First Name || ========================== */} - - - First Name - { - setRiderdata({ - ...riderdata, - firstname: e.target.value - }); - }} - placeholder="Name" - autoComplete="off" - /> - - - {/* ========================== || Last Name || ========================== */} - - - Last Name - { - setRiderdata({ - ...riderdata, - lastname: e.target.value - }); - }} - value={riderdata?.lastname} - autoComplete="off" - /> - - - {/* ========================== || Phone Number || ========================== */} - - - Phone Number - - - { - setRiderdata({ - ...riderdata, - contactno: e.target.value - }); - }} - value={riderdata?.contactno} - autoComplete="off" - // disabled - sx={{ cursor: 'not-allowed' }} - /> - - - {' '} - {/* ========================== || Email Address || ========================== */} - - - Email Address - { - setRiderdata({ - ...riderdata, - email: e.target.value - }); - }} - value={riderdata?.email} - autoComplete="off" - /> - - - {/* ========================== ||Address || ========================== */} - - - Address - { - setAddress(e.target.value); - }} - inputRef={materialRef} - /> - - - {/* ========================== || Location|| ========================== */} - - - Location - { - setRiderdata({ - ...riderdata, - suburb: e.target.value - }); - setSuburb(e.target.value); - }} - value={suburb} - autoComplete="off" - /> - - - {/* ========================== || City|| ========================== */} - - - City - { - setRiderdata({ - ...riderdata, - city: e.target.value - }); - setCity(e.target.value); - }} - value={city} - autoComplete="off" - /> - - - {/* ========================== || State|| ========================== */} - - - State - { - setRiderdata({ - ...riderdata, - state: e.target.value - }); - setState(e.target.value); - }} - value={state} - autoComplete="off" - /> - - - {/* ========================== || Identification No|| ========================== */} - - - Identification No - { - setRiderdata({ - ...riderdata, - identificationno: e.target.value - }); - }} - value={riderdata?.identificationno} - autoComplete="off" - /> - - - {/* ========================== || Choose Partner|| ========================== */} - - - Choose Partner - `${option.locationname}`} - sx={{ width: { xs: '100%', sm: 300 }, height: '30px', ml: { xs: 0, sm: 3 }, zIndex: '100' }} - onChange={(event, value) => { - if (value) { - console.log(value); - setLocoName(value.locationname); - fetchridershifts(value.applocationid); - setRiderdata({ - ...riderdata, - partnerid: value.partnerid, - applocationid: value.applocationid - }); - } else { - setPartner({}); - } - }} - renderInput={(params) => } - /> - {/* } - label={locaName} - options={partnerlist} - getOptionLabel={(option) => `${option.locationname}`} - onChange={(e, val) => { - if (val) { - setPartner(val); - setRiderdata({ - ...riderdata, - partnerid: val.partnerid - }); - } else { - setPartner({}); - } - }} - freeSolo - /> */} - - - - - {/* || =========================================== || Charges || =========================================== || */} - - - Charges - - } - > - - {/* ========================== || Shift Type || ========================== */} - - - - Shift Type - - ( - - )} - // disabled - options={shiftlist} - onChange={(e, val) => { - if (val) { - console.log('shift', val); - setRiderdata({ - ...riderdata, - shiftid: val.shiftid, - additionalkm: val.additionalkm, - additionalcharges: val.additionalcharges, - basefare: val.basefare, - starttime: val.starttime, - endtime: val.endtime - }); - - setBasefare(val.basefare); - setAdditionalkms(val.additionalkm); - setOthercharges(val.additionalcharges); - setShift(val); - } else { - setBasefare(''); - setAdditionalkms(''); - setOthercharges(''); - setShift({}); - } - }} - freeSolo - /> - - - {/* ========================== || Base Fare || ========================== */} - - - - Base Fare - { - setRiderdata({ - ...riderdata, - basefare: e.target.value - }); - setBasefare(e.target.value); - }} - value={riderdata?.basefare} - autoComplete="off" - disabled - /> - - - {/* ========================== || Additional Kms || ========================== */} - - - - Additional Kms - { - setRiderdata({ - ...riderdata, - additionalkm: e.target.value - }); - setAdditionalkms(e.target.value); - }} - value={riderdata?.additionalkm} - autoComplete="off" - disabled - /> - - - {/* ========================== || Other Charges || ========================== */} - - - - Other Charges - { - setRiderdata({ - ...riderdata, - additionalcharges: e.target.value - }); - setOthercharges(e.target.value); - }} - value={riderdata?.additionalcharges} - autoComplete="off" - disabled - /> - - - - - - {/* || =========================================== || Bank Details || =========================================== || */} - - - Bank Details - - } - > - - {' '} - {/* ========================== || Account No|| ========================== */} - - - Account No - { - setRiderdata({ - ...riderdata, - accountno: e.target.value - }); - setAccountno(e.target.value); - }} - autoComplete="off" - /> - - {' '} - {/* ========================== || Account Name || ========================== */} - - - Account Name - { - setRiderdata({ - ...riderdata, - accountname: e.target.value - }); - setAccountname(e.target.value); - }} - autoComplete="off" - /> - - {' '} - {/* ========================== || Account Type || ========================== */} - - - Account Type - - ( - - )} - // disabled - options={accountlist} - // value={clientdetail} - onChange={(e, val) => { - console.log('ac type', val); - if (val) { - setRiderdata({ - ...riderdata, - accounttype: val.label - }); - setAccount(val); - setAccountType(val.label); - // fetchroles(val.tenantid); - } else { - setAccount({}); - } - }} - freeSolo - /> - - {' '} - {/* ========================== || Bank Name|| ========================== */} - - - Bank Name - { - setRiderdata({ - ...riderdata, - bankname: e.target.value - }); - setBankname(e.target.value); - }} - autoComplete="off" - /> - - {' '} - {/* ========================== || IFSC Code || ========================== */} - - - IFSC Code - { - setRiderdata({ - ...riderdata, - ifsccode: e.target.value - }); - setIfsc(e.target.value); - }} - autoComplete="off" - /> - - - {/* ========================== || Branch || ========================== */} - - - Branch - { - setRiderdata({ - ...riderdata, - branch: e.target.value - }); - setBranch(e.target.value); - }} - autoComplete="off" - /> - - - - - - {/* || =========================================== || Vehicle Details || =========================================== || */} - - - Vehicle Details - - } - > - - {/* ========================== || Vehicle Name || ========================== */} - - - Vehicle Name - ( - - )} - // disabled - options={vehiclelist} - // value={clientdetail} - onChange={(e, val) => { - if (val) { - console.log('vehi', val); - setVehicle(val); - setRiderdata({ - ...riderdata, - vehiclename: val.label, - vehicleid: val.apptypeid - }); - - // fetchroles(val.tenantid); - } else { - setVehicle({}); - } - }} - freeSolo - /> - - {' '} - {/* ========================== || Vehicle No || ========================== */} - - - Vehicle No - { - setRiderdata({ - ...riderdata, - vehicleno: e.target.value - }); - }} - value={riderdata?.vehicleno} - autoComplete="off" - /> - - {' '} - {/* ========================== || Model Year || ========================== */} - - - Model Year - { - setRiderdata({ - ...riderdata, - model: e.target.value - }); - setModelyear(e.target.value); - }} - value={riderdata?.model} - autoComplete="off" - /> - - {' '} - {/* ========================== || Vehicle Color || ========================== */} - - - Vehicle Color - { - setRiderdata({ - ...riderdata, - color: e.target.value - }); - setVehiclecolor(e.target.value); - }} - value={riderdata?.color} - autoComplete="off" - /> - - {' '} - {/* ========================== || License No || ========================== */} - - - License No - { - setRiderdata({ - ...riderdata, - licenseno: e.target.value - }); - }} - value={riderdata?.licenseno} - autoComplete="off" - /> - - {' '} - {/* ========================== || Insurance No || ========================== */} - - - Insurance No - { - setRiderdata({ - ...riderdata, - insuranceno: e.target.value - }); - }} - value={riderdata?.insuranceno} - autoComplete="off" - /> - - {' '} - {/* ========================== || Insurance Expiry Date || ========================== */} - - - Insurance Expiry Date - - { - setExpirydate(dayjs(e.$d).format('YYYY-MM-DD 00:00:00')); - setRiderdata({ - ...riderdata, - insurancedate: dayjs(e.$d).format('YYYY-MM-DD 00:00:00') - }); - }} - sx={{ width: '100%' }} - // disablePast - // minDate={dayjs().add(1, 'day')} - /> - - - - - - - {/* ================= FIXED BOTTOM ACTION ================= */} + + + + + Name + setRiderdata({ ...riderdata, displayname: e.target.value })} + placeholder="Name" + autoComplete="off" + /> + + + + + Phone Number + + + { + if (e.target.value.toString().length <= 10) { + setRiderdata({ ...riderdata, phone: e.target.value }); + } + }} + value={riderdata?.phone || ''} + autoComplete="off" + /> + + + + + + Hub + + + + + + Availability Status + + + + - {/* ========================== || Update || ========================== */} { backgroundColor: 'secondary.lighter', p: 2, zIndex: 10, - border: ' 1px solid ', + border: '1px solid', borderColor: '#E6EBF1', borderTop: 'none' }} > - diff --git a/src/pages/nearle/riders/riders.js b/src/pages/nearle/riders/riders.js index 06e90c2..e10f3b2 100644 --- a/src/pages/nearle/riders/riders.js +++ b/src/pages/nearle/riders/riders.js @@ -1,13 +1,12 @@ import * as React from 'react'; -import { useState, useEffect, useRef, Fragment } from 'react'; -import Geocode from 'react-geocode'; +import { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; import { Avatar, Paper, Stack, - Typography, + Typography, Table, TableCell, TableBody, @@ -17,49 +16,27 @@ import { Tooltip, TableContainer, Backdrop, - Collapse, Grid, Box, Skeleton, useMediaQuery, - ToggleButtonGroup, - ToggleButton, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Button, - Autocomplete, - TextField + Button } from '@mui/material'; import { useTheme } from '@mui/material/styles'; -var utc = require('dayjs/plugin/utc'); -import dayjs from 'dayjs'; -dayjs.extend(utc); -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { MdMyLocation, MdCheckCircle, - MdCancel, MdGroups, MdOutlineGroups, MdOutlineCheckCircle, MdOutlineCancel, MdEdit, - MdKeyboardArrowDown, - MdKeyboardArrowUp, - MdLocationOn, - MdBatteryStd, - MdPowerSettingsNew, - MdSpeed, - MdGpsFixed, MdAccessTime, MdInventory2, MdTwoWheeler, - MdArrowForward, - MdDelete + MdPowerSettingsNew, + MdAdd, + MdStar } from 'react-icons/md'; import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; import PageHeader from 'components/nearle_components/PageHeader'; @@ -67,12 +44,11 @@ import StatCard from 'components/nearle_components/StatCard'; import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard'; import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar'; import CircularLoader from 'components/CircularLoader'; -import { fetchAllRiders, getallridersummary, getriderstatus } from 'pages/api/api'; +import { fetchAllRiders, getallridersummary } from 'pages/api/api'; import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'; import LoaderWithImage from 'components/nearle_components/LoaderWithImage'; import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton'; import { OpenToast } from 'components/third-party/OpenToast'; -import RiderSubstitution from './RiderSubstitution'; // ============================================================================ // Design tokens — shared with the deliveries / tenants / customers pages so @@ -130,30 +106,27 @@ const AccentAvatar = ({ color, selected, size = 24, children }) => ( ); -// Status palette — semantic only (do NOT swap for brand purple). Used by the -// per-row status badges and the lifecycle tabs (ALL, Active). +// Status palette — semantic only (do NOT swap for brand purple). Keyed by +// Doormile's miler availabilitystatus values. const STATUS_META = { - active: { label: 'Active', color: '#10b981', icon: MdCheckCircle }, - inactive: { label: 'Inactive', color: '#ef4444', icon: MdCancel }, - online: { label: 'Online', color: '#10b981', icon: MdCheckCircle }, - offline: { label: 'Offline', color: '#ef4444', icon: MdCancel }, - idle: { label: 'Idle', color: '#f59e0b', icon: MdAccessTime }, - unknown: { label: 'Unknown', color: '#94a3b8', icon: MdInventory2 } + available: { label: 'Available', color: '#10b981', icon: MdCheckCircle }, + assigned: { label: 'Assigned', color: '#6366f1', icon: MdTwoWheeler }, + on_break: { label: 'On Break', color: '#f59e0b', icon: MdAccessTime }, + offline: { label: 'Offline', color: '#94a3b8', icon: MdPowerSettingsNew }, + unknown: { label: 'Unknown', color: '#94a3b8', icon: MdInventory2 } }; // Pill-tab definitions for the rider listing tabs. Keeps brand purple for the -// "ALL" view and emerald for "Active" so the colour matches the count's meaning. +// "ALL" view and emerald for "Available" so the colour matches the count's meaning. const TAB_META = [ - { key: 0, label: 'All Riders', color: BRAND, icon: MdGroups, countKey: 'total' }, - { key: 1, label: 'Active', color: '#10b981', icon: MdCheckCircle, countKey: 'active' }, - { key: 2, label: 'Substitutes', color: '#8b5cf6', icon: MdTwoWheeler, countKey: 'substitute' }, - { key: 3, label: 'Substitution History', color: '#f59e0b', icon: MdAccessTime, countKey: 'history' } + { key: 0, label: 'All Milers', color: BRAND, icon: MdGroups, countKey: 'total' }, + { key: 1, label: 'Available', color: '#10b981', icon: MdCheckCircle, countKey: 'active' } ]; const KPI_META = (summary) => [ - { key: 'total', label: 'Total Riders', color: BRAND, icon: MdOutlineGroups, value: summary?.total ?? 0 }, - { key: 'active', label: 'Active Riders', color: '#10b981', icon: MdOutlineCheckCircle, value: summary?.active ?? 0 }, - { key: 'inactive', label: 'Inactive Riders', color: '#ef4444', icon: MdOutlineCancel, value: summary?.inactive ?? 0 } + { key: 'total', label: 'Total Milers', color: BRAND, icon: MdOutlineGroups, value: summary?.total ?? 0 }, + { key: 'active', label: 'Available Milers', color: '#10b981', icon: MdOutlineCheckCircle, value: summary?.active ?? 0 }, + { key: 'inactive', label: 'Offline Milers', color: '#ef4444', icon: MdOutlineCancel, value: summary?.inactive ?? 0 } ]; const Riders = () => { @@ -169,333 +142,10 @@ const Riders = () => { const [appId, setAppId] = useState(0); const [tabvalue, setTabvalue] = useState(0); const roleid = localStorage.getItem('roleid'); - const totalCols = roleid == 1 ? 11 : 10; - const [logsRow, setLogsRow] = useState(null); - const [riderLogsdata, setRiderLogsdata] = useState(null); - const [historyDate, setHistoryDate] = useState(dayjs()); - const [historyStatus, setHistoryStatus] = useState('all'); - const [selectedDate, setSelectedDate] = useState(dayjs()); - const [editingSub, setEditingSub] = useState(null); - const [newSubRider, setNewSubRider] = useState(null); - - const getResolvedPartnerId = () => { - let partnerId = ''; - try { - const rawLocs = localStorage.getItem('applocations'); - if (rawLocs) { - const locs = JSON.parse(rawLocs); - const currentLoc = locs.find((l) => l.applocationid === appId); - if (currentLoc && currentLoc.partnerid) { - partnerId = currentLoc.partnerid; - } else { - const firstValidLoc = locs.find((l) => l.partnerid); - if (firstValidLoc && firstValidLoc.partnerid) { - partnerId = firstValidLoc.partnerid; - } - } - } - } catch (e) { - console.error('Error parsing applocations', e); - } - - if (!partnerId) { - const savedPartnerId = localStorage.getItem('partnerid'); - if (savedPartnerId && savedPartnerId !== 'undefined' && savedPartnerId !== 'null') { - partnerId = savedPartnerId; - } - } - - if (!partnerId || partnerId === '0' || partnerId === 0) { - partnerId = 44; - } - return partnerId; - }; - - const getHistoryStatusColor = (status) => { - const s = (status || '').toLowerCase(); - if (s === 'cancelled' || s === 'deleted' || s === 'inactive') return '#ef4444'; // Red - if (s === 'scheduled') return '#0ea5e9'; // Blue - if (s === 'active' || s === 'completed') return '#10b981'; // Green - return '#64748b'; // Gray - }; - - const handleDeleteSubstitution = async (row) => { - if (window.confirm(`Are you sure you want to delete the substitution for ${row.absent_rider_name}?`)) { - try { - const resolvedTenantId = row.tenant_id || row.tenantid || getResolvedPartnerId(); - const url = `${process.env.REACT_APP_URL}/substitutions/${row.id}?tenant_id=${resolvedTenantId}`; - const res = await axios.delete(url); - if (res.data && res.data.status) { - OpenToast('Substitution deleted successfully!', 'success', 2000); - } else { - OpenToast(res.data?.message || 'Failed to delete substitution.', 'error', 2000); - } - } catch (err) { - console.error(err); - OpenToast(err.response?.data?.message || err.message || 'Failed to delete substitution.', 'error', 2000); - } finally { - queryClient.invalidateQueries({ queryKey: ['substitutionsHistory'] }); - queryClient.invalidateQueries({ queryKey: ['activeSubstitutionsToday'] }); - } - } - }; - - const handleUpdateSubstitutionSubmit = async () => { - if (!newSubRider) { - OpenToast('Please select a substitute rider', 'error', 2000); - return; - } - try { - const resolvedTenantId = editingSub.tenant_id || editingSub.tenantid || getResolvedPartnerId(); - const url = `${process.env.REACT_APP_URL}/substitutions/${editingSub.id}`; - const payload = { - tenant_id: parseInt(resolvedTenantId, 10), - sub_rider_id: parseInt(newSubRider.userid, 10), - sub_rider_name: newSubRider.username || newSubRider.fullname || `Rider #${newSubRider.userid}` - }; - const res = await axios.put(url, payload); - if (res.data && res.data.status) { - OpenToast('Substitution updated successfully!', 'success', 2000); - } else { - OpenToast(res.data?.message || 'Failed to update substitution.', 'error', 2000); - } - } catch (err) { - console.error(err); - OpenToast(err.response?.data?.message || err.message || 'Failed to update substitution.', 'error', 2000); - } finally { - setEditingSub(null); - setNewSubRider(null); - queryClient.invalidateQueries({ queryKey: ['substitutionsHistory'] }); - queryClient.invalidateQueries({ queryKey: ['activeSubstitutionsToday'] }); - } - }; - - const [substituteAssignments, setSubstituteAssignments] = useState(() => { - try { - const saved = localStorage.getItem('rider_substitutions'); - return saved ? JSON.parse(saved) : {}; - } catch { - return {}; - } - }); - - const handleAssignSubstitute = (activeRiderId, substituteRider) => { - const updated = { - ...substituteAssignments, - [activeRiderId]: substituteRider - }; - setSubstituteAssignments(updated); - localStorage.setItem('rider_substitutions', JSON.stringify(updated)); - if (substituteRider) { - OpenToast(`Assigned ${substituteRider.username || substituteRider.fullname || 'Rider'} as substitute`, 'success', 2000); - } else { - OpenToast(`Removed substitute assignment`, 'info', 2000); - } - }; - - const handleFinalizeSuccess = () => { - setSubstituteAssignments({}); - localStorage.removeItem('rider_substitutions'); - queryClient.invalidateQueries({ queryKey: ['substitutionsHistory'] }); - queryClient.invalidateQueries({ queryKey: ['subRidersLogs'] }); - queryClient.invalidateQueries({ queryKey: ['activeSubstitutionsToday'] }); - }; - - const { data: substituteRidersList } = useQuery({ - queryKey: ['allRidersForSub', appId], - queryFn: async () => { - try { - // Resolve partner ID dynamically - let partnerId = ''; - try { - const rawLocs = localStorage.getItem('applocations'); - if (rawLocs) { - const locs = JSON.parse(rawLocs); - const currentLoc = locs.find((l) => l.applocationid === appId); - if (currentLoc && currentLoc.partnerid) { - partnerId = currentLoc.partnerid; - } else { - const firstValidLoc = locs.find((l) => l.partnerid); - if (firstValidLoc && firstValidLoc.partnerid) { - partnerId = firstValidLoc.partnerid; - } - } - } - } catch (e) { - console.error('Error parsing applocations', e); - } - - if (!partnerId) { - const savedPartnerId = localStorage.getItem('partnerid'); - if (savedPartnerId && savedPartnerId !== 'undefined' && savedPartnerId !== 'null') { - partnerId = savedPartnerId; - } - } - - if (!partnerId || partnerId === '0' || partnerId === 0) { - partnerId = 44; // final fallback - } - - const url = `${process.env.REACT_APP_URL}/partners/getallriders/?applocationid=0&partnerid=${partnerId}&pagesize=1000&pageno=1&status=`; - const res = await axios.get(url); - const allRiders = res.data.details || []; - console.log('Substitute query - all riders returned from API:', allRiders.map(r => ({ userid: r.userid, username: r.username, partnerid: r.partnerid }))); - const allowedIds = [1121, 772, 1116]; - const filtered = allRiders.filter((rider) => allowedIds.includes(parseInt(rider.userid))); - console.log('Substitute query - filtered allowed riders:', filtered); - return filtered; - } catch (err) { - console.error(err); - return []; - } - } - }); - - const { data: subRidersLogsData, isLoading: subRidersLogsLoading } = useQuery({ - queryKey: ['subRidersLogs', appId, debouncedSearch, selectedDate.format('YYYY-MM-DD')], - queryFn: async () => { - try { - const dateStr = selectedDate.format('YYYY-MM-DD'); - - // Resolve partner ID dynamically - let partnerId = ''; - try { - const rawLocs = localStorage.getItem('applocations'); - if (rawLocs) { - const locs = JSON.parse(rawLocs); - const currentLoc = locs.find((l) => l.applocationid === appId); - if (currentLoc && currentLoc.partnerid) { - partnerId = currentLoc.partnerid; - } else { - const firstValidLoc = locs.find((l) => l.partnerid); - if (firstValidLoc && firstValidLoc.partnerid) { - partnerId = firstValidLoc.partnerid; - } - } - } - } catch (e) { - console.error('Error parsing applocations', e); - } - - if (!partnerId) { - const savedPartnerId = localStorage.getItem('partnerid'); - if (savedPartnerId && savedPartnerId !== 'undefined' && savedPartnerId !== 'null') { - partnerId = savedPartnerId; - } - } - - if (!partnerId || partnerId === '0' || partnerId === 0) { - partnerId = 44; - } - - const url = `${process.env.REACT_APP_URL2}/partners/getriderlogs/?applocationid=${appId}&partnerid=${partnerId}&fromdate=${dateStr}&todate=${dateStr}&keyword=${debouncedSearch}`; - const res = await axios.get(url); - return res.data.details || []; - } catch (err) { - console.error(err); - return []; - } - } - }); - - useQuery({ - queryKey: ['activeSubstitutionsToday', appId, selectedDate.format('YYYY-MM-DD')], - queryFn: async () => { - try { - let partnerId = ''; - try { - const rawLocs = localStorage.getItem('applocations'); - if (rawLocs) { - const locs = JSON.parse(rawLocs); - const currentLoc = locs.find((l) => l.applocationid === appId); - if (currentLoc && currentLoc.partnerid) { - partnerId = currentLoc.partnerid; - } else { - const firstValidLoc = locs.find((l) => l.partnerid); - if (firstValidLoc && firstValidLoc.partnerid) { - partnerId = firstValidLoc.partnerid; - } - } - } - } catch (e) { - console.error('Error parsing applocations', e); - } - - if (!partnerId) { - const savedPartnerId = localStorage.getItem('partnerid'); - if (savedPartnerId && savedPartnerId !== 'undefined' && savedPartnerId !== 'null') { - partnerId = savedPartnerId; - } - } - - if (!partnerId || partnerId === '0' || partnerId === 0) { - partnerId = 44; - } - - const dateStr = selectedDate.format('YYYY-MM-DD'); - const url = `${process.env.REACT_APP_URL}/substitutions?tenant_id=${partnerId}&from_date=${dateStr}&to_date=${dateStr}`; - const res = await axios.get(url); - return res.data.details || []; - } catch (err) { - console.error(err); - return []; - } - } - }); - - const { data: subHistoryData, isLoading: subHistoryLoading } = useQuery({ - queryKey: ['substitutionsHistory', appId, historyDate, historyStatus], - queryFn: async () => { - try { - let partnerId = ''; - try { - const rawLocs = localStorage.getItem('applocations'); - if (rawLocs) { - const locs = JSON.parse(rawLocs); - const currentLoc = locs.find((l) => l.applocationid === appId); - if (currentLoc && currentLoc.partnerid) { - partnerId = currentLoc.partnerid; - } else { - const firstValidLoc = locs.find((l) => l.partnerid); - if (firstValidLoc && firstValidLoc.partnerid) { - partnerId = firstValidLoc.partnerid; - } - } - } - } catch (e) { - console.error('Error parsing applocations', e); - } - - if (!partnerId) { - const savedPartnerId = localStorage.getItem('partnerid'); - if (savedPartnerId && savedPartnerId !== 'undefined' && savedPartnerId !== 'null') { - partnerId = savedPartnerId; - } - } - - if (!partnerId || partnerId === '0' || partnerId === 0) { - partnerId = 44; - } - - const dateStr = historyDate.format('YYYY-MM-DD'); - let url = `${process.env.REACT_APP_URL}/substitutions?tenant_id=${partnerId}&from_date=${dateStr}&to_date=${dateStr}`; - if (historyStatus !== 'all') { - url += `&status=${historyStatus}`; - } - const res = await axios.get(url); - return res.data.details || []; - } catch (err) { - console.error(err); - return []; - } - } - }); - - Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY); + const totalCols = roleid == 1 ? 9 : 8; const handleChangetab = (i) => { setTabvalue(i); - setLogsRow(null); }; // ==============================|| getallridersummary||============================== // @@ -504,32 +154,6 @@ const Riders = () => { queryFn: getallridersummary }); - // ==============================|| getRiderLogs (riders)||============================== // - const getRiderLogs = async (userid) => { - try { - const res = await axios.get(`${process.env.REACT_APP_URL}/utils/getriderperiodiclogs?userid=${userid}`); - if (res.data.data.length == 0) { - setLogsRow(null); - OpenToast(res.data.message, 'error', 2000); - } else { - setRiderLogsdata(res.data.data); - } - } catch (err) { - OpenToast(err.message, 'error', 2000); - } - }; - - // ==============================|| getriderstatus||============================== // - const { - data: ridersStatus, - isLoading: riderStatusLoading, - isError: riderstatusIsError, - error: riderStatusError - } = useQuery({ - queryKey: ['ridersStatus'], - queryFn: getriderstatus - }); - // ==============================|| fetchAllRiders||============================== // const { data: allRidersData, @@ -574,51 +198,66 @@ const Riders = () => { } }; - const errMessage = riderstatusIsError ? riderStatusError : null; - useEffect(() => { - if (errMessage) { - OpenToast(errMessage, 'error', 2000); - } - }, [errMessage]); - - // Per-row status meta — falls back to "unknown" if the rider's state key - // isn't in the palette (e.g. brand-new status string from the backend). + // Per-row status meta — falls back to "unknown" if the miler's + // availabilitystatus isn't in the palette. const getRowStatusMeta = (row) => { - if (tabvalue == 0) { - const key = (row?.status || '').toLowerCase() === 'active' ? 'active' : 'inactive'; - return STATUS_META[key] || STATUS_META.unknown; - } - const state = ridersStatus?.find((s) => s.userid === row?.userid); - const key = (state?.status || 'unknown').toLowerCase(); + const key = (row?.availabilitystatus || 'unknown').toLowerCase(); return STATUS_META[key] || STATUS_META.unknown; }; + const handleDeactivate = async (row) => { + if (!window.confirm(`Set ${row.displayname || `Miler #${row.userid}`} offline?`)) return; + try { + await axios.patch(`${process.env.REACT_APP_URL}/admin/milers/${row.userid}`, { availabilitystatus: 'Offline' }); + OpenToast('Miler set offline', 'success', 2000); + queryClient.invalidateQueries({ queryKey: ['allriders'] }); + } catch (err) { + OpenToast(err.response?.data?.message || err.message || 'Failed to update miler.', 'error', 2000); + } + }; + return ( <> - theme.zIndex.drawer + 1 }} - open={allRidersLoading || riderSummarysLoading || riderStatusLoading || (tabvalue === 2 && subRidersLogsLoading) || (tabvalue === 3 && subHistoryLoading)} - > + theme.zIndex.drawer + 1 }} open={allRidersLoading || riderSummarysLoading}> {/* ============================================= || Header | ============================================= */} } - placeholder="Select Zone" - paperComponent={SoftPaper} - sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }} - /> + + } + placeholder="Select Zone" + paperComponent={SoftPaper} + sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }} + /> + {roleid == 1 && ( + + )} + } /> @@ -628,13 +267,7 @@ const Riders = () => { const Icon = item.icon; return ( - } - color={item.color} - loading={riderSummarysLoading} - /> + } color={item.color} loading={riderSummarysLoading} /> ); })} @@ -656,13 +289,7 @@ const Riders = () => { background: '#fff' }} > - + { {TAB_META.map((t) => { const Icon = t.icon; const active = tabvalue === t.key; - const count = t.key === 2 - ? (subRidersLogsData?.length || 0) - : t.key === 3 - ? (subHistoryData?.length || 0) - : (allRidersSummary?.[t.countKey] ?? 0); - const countLoading = t.key === 2 - ? subRidersLogsLoading - : t.key === 3 - ? subHistoryLoading - : riderSummarysLoading; + const count = allRidersSummary?.[t.countKey] ?? 0; return ( { - {t.label} + {t.label} { border: 'none' }} > - {countLoading ? : count} + {riderSummarysLoading ? : count} ); @@ -755,7 +373,7 @@ const Riders = () => { value={searchword} onChange={setSearchword} onDebouncedChange={setDebouncedSearch} - placeholder="Search riders (ctrl+k)" + placeholder="Search milers (ctrl+k)" sx={{ m: 0, width: '100%', @@ -771,356 +389,7 @@ const Riders = () => { - {tabvalue === 3 && ( - - - - - Status: - - val && setHistoryStatus(val)} - aria-label="status selection" - size="small" - sx={{ - '& .MuiToggleButton-root': { - borderRadius: '8px', - mx: 0.5, - border: `1px solid ${DT.borderSubtle}`, - color: DT.textSecondary, - fontWeight: 600, - textTransform: 'capitalize', - px: 2, - py: 0.5, - '&.Mui-selected': { - bgcolor: BRAND, - color: '#fff', - '&:hover': { - bgcolor: '#900E1D' - } - } - } - }} - > - All - Scheduled - - - - - newValue && setHistoryDate(newValue)} - slotProps={{ - textField: { - size: 'small', - sx: { - width: 180, - '& .MuiOutlinedInput-root': { - borderRadius: '20px', - '& fieldset': { borderColor: DT.borderSubtle }, - '&:hover fieldset': { borderColor: BRAND }, - '&.Mui-focused fieldset': { borderColor: BRAND } - } - } - } - }} - /> - - - - - )} - - {tabvalue === 3 ? ( - - {subHistoryData?.length === 0 ? ( - - - - - - No substitution history to show - - - Historical substitution logs will be listed here. - - - ) : isMobile ? ( - - {subHistoryData?.map((row, index) => ( - - - - {dayjs(row.sub_date).format('DD MMM YYYY')} - - - {row.status || 'scheduled'} - - - - - - Absent Rider - - - {row.absent_rider_name} - - - #{row.absent_rider_id} - - - - - - Substitute - - - {row.sub_rider_name} - - - #{row.sub_rider_id} - - - - {row.reason && ( - - - Reason: {row.reason} - - - )} - - } - /> - ))} - - ) : ( - - - - - Date - Absent Rider - - Substitute Rider - Reason - Status - Action - - - - {subHistoryData?.map((row, index) => ( - - - - {dayjs(row.sub_date).format('DD MMM YYYY')} - - - - - - {(row.absent_rider_name || '?').charAt(0).toUpperCase()} - - - - {row.absent_rider_name} - - - #{row.absent_rider_id} - - - - - - - - - - - {(row.sub_rider_name || '?').charAt(0).toUpperCase()} - - - - {row.sub_rider_name} - - - #{row.sub_rider_id} - - - - - - - {row.reason || '—'} - - - - - {row.status || 'scheduled'} - - - - {(row.status || '').toLowerCase() !== 'cancelled' && ( - - - { - setEditingSub(row); - setNewSubRider(null); - }} - > - - - - - { - handleDeleteSubstitution(row); - }} - > - - - - - )} - - - ))} - -
    -
    - )} - - ) : tabvalue === 2 ? ( - - ) : ( - { - Loading riders… + Loading milers… )} @@ -1165,12 +434,10 @@ const Riders = () => { - No riders to show + No milers to show - {searchword - ? 'Try a different keyword.' - : `No ${tabvalue === 0 ? '' : 'active '}riders for this zone.`} + {searchword ? 'Try a different keyword.' : `No ${tabvalue === 0 ? '' : 'available '}milers for this zone.`}
    )} @@ -1179,12 +446,10 @@ const Riders = () => { rows?.map((row, index) => { const statusMeta = getRowStatusMeta(row); const StatusIcon = statusMeta.icon; - const expanded = logsRow === row.userid; return ( @@ -1249,28 +514,6 @@ const Riders = () => { >
    - {tabvalue != 0 && ( - { - if (row.userid == logsRow) { - setLogsRow(null); - } else { - setLogsRow(row.userid); - getRiderLogs(row.userid); - } - }} - > - {expanded ? : } - - )} )} @@ -1287,14 +530,14 @@ const Riders = () => { flexShrink: 0 }} > - {(row.fullname || row.username || '?').charAt(0).toUpperCase()} + {(row.displayname || '?').charAt(0).toUpperCase()} - {row.username || '—'} + {row.displayname || '—'} - {row.contactno || '—'} + {row.phone || '—'} @@ -1302,136 +545,18 @@ const Riders = () => { } > - - - {row.suburb || (row.address ? row.address.slice(0, 20) + '…' : '—')} - - - {row.city || ''} - - - - - - - + + + + - {row.vehicleno || '—'} + {row.rating ?? '—'} - - - - - {row.starttime ? dayjs(`${dayjs().format('MM-DD-YYYY')} ${row.starttime}`).format('hh:mm A') : '—'} - - - {row.endtime ? dayjs(`${dayjs().format('MM-DD-YYYY')} ${row.endtime}`).format('hh:mm A') : '—'} - - - - - - + + - - {expanded && tabvalue !== 0 && ( - - - - - - - Live telemetry — {row.username || `Rider #${row.userid}`} - - - - - - - - - - - - - - )} ); })} @@ -1442,85 +567,78 @@ const Riders = () => { ) : ( - No more riders + No more milers )} )} ) : ( - - - - # - ID - Rider - Address - Vehicle - Shift - Time - Fare - Fuel - Status - {roleid == 1 && Action} - - - - {allRidersLoading && } - - {rows?.length === 0 && !allRidersLoading && ( - - - - - - - - No riders to show - - - {searchword - ? 'Try a different keyword.' - : `No ${tabvalue === 0 ? '' : 'active '}riders for this zone.`} - - - +
    + + + # + ID + Miler + Hub + Rating + Completed + Cancelled + Status + {roleid == 1 && Action} - )} + + + {allRidersLoading && } - {rows?.length !== 0 && - rows?.map((row, index) => { - const statusMeta = getRowStatusMeta(row); - const StatusIcon = statusMeta.icon; - const expanded = logsRow === row.userid; - return ( - + {rows?.length === 0 && !allRidersLoading && ( + + + + + + + + No milers to show + + + {searchword ? 'Try a different keyword.' : `No ${tabvalue === 0 ? '' : 'available '}milers for this zone.`} + + + + + )} + + {rows?.length !== 0 && + rows?.map((row, index) => { + const statusMeta = getRowStatusMeta(row); + const StatusIcon = statusMeta.icon; + return ( @@ -1561,102 +679,39 @@ const Riders = () => { border: `1px solid ${edge(BRAND)}` }} > - {(row.fullname || row.username || '?').charAt(0).toUpperCase()} + {(row.displayname || '?').charAt(0).toUpperCase()} - - {row.username || '—'} + + {row.displayname || '—'} - {row.contactno || '—'} + {row.phone || '—'} - - - - - {row.suburb || (row.address ? row.address.slice(0, 20) + '…' : '—')} - - - {row.city || ''} - - - - - - - - - - - {row.vehicleno || '—'} - - - - #{row.shiftid ?? '—'} + #{row.hubid ?? '—'} + + + + + + + {row.rating ?? '—'} + + + + + + {row.completedorders ?? 0} - - - {row.starttime ? dayjs(`${dayjs().format('MM-DD-YYYY')} ${row.starttime}`).format('hh:mm A') : '—'} - - - {row.endtime ? dayjs(`${dayjs().format('MM-DD-YYYY')} ${row.endtime}`).format('hh:mm A') : '—'} - - - - - {row.basefare ?? '—'} - - - - - {row.fuelcharge ?? '—'} + {row.cancelledorders ?? 0} @@ -1686,7 +741,7 @@ const Riders = () => { {roleid == 1 && ( - + { - {tabvalue != 0 && ( - - { - if (row.userid == logsRow) { - setLogsRow(null); - } else { - setLogsRow(row.userid); - getRiderLogs(row.userid); - } - }} - > - {expanded ? : } - - - )} + + handleDeactivate(row)} + > + + + )} + ); + })} - {/* ============ Collapsible row — live rider logs ============ */} - {expanded && tabvalue !== 0 && ( - - - - - - - - - - Live telemetry — {row.username || `Rider #${row.userid}`} - - - - - - - - - - - - - - - - - )} - - ); - })} - - {rows?.length !== 0 && ( - - -
    - {isFetchingNextPage || hasNextPage ? ( - - ) : ( - - No more riders - - )} -
    -
    -
    - )} -
    -
    + {rows?.length !== 0 && ( + + +
    + {isFetchingNextPage || hasNextPage ? ( + + ) : ( + + No more milers + + )} +
    +
    +
    + )} + + )} - )} - - setEditingSub(null)} - maxWidth="xs" - fullWidth - PaperProps={{ - sx: { - borderRadius: `${DT.radiusCard / 8}px`, - p: 1 - } - }} - > - Update Substitution - - - - Choose a new substitute rider for {editingSub?.absent_rider_name}. - - option?.userid === value?.userid} - getOptionLabel={(option) => `${option.username || option.fullname || ''} (#${option.userid})`} - value={newSubRider} - onChange={(event, newValue) => setNewSubRider(newValue)} - renderInput={(params) => ( - - )} - /> - - - - - - - ); }; -// Inline stat chip used in the rider-logs collapse row. Mirrors the StatChip -// pattern from the pricing page so the telemetry block reads at a glance. -const LogChip = ({ color, icon: Icon, label, value }) => ( - - - - - - - - {label} - - - {value} - - - - -); - export default Riders; diff --git a/src/utils/axios.js b/src/utils/axios.js index 6ee9920..0db0554 100644 --- a/src/utils/axios.js +++ b/src/utils/axios.js @@ -4,6 +4,12 @@ const axiosServices = axios.create({ baseURL: process.env.REACT_APP_API_URL || ' // ==============================|| AXIOS - FOR MOCK SERVICES ||============================== // +axiosServices.interceptors.request.use((config) => { + const token = localStorage.getItem('token'); + if (token) config.headers.Authorization = `Bearer ${token}`; + return config; +}); + axiosServices.interceptors.response.use( (response) => response, (error) => { diff --git a/src/utils/locales/en.json b/src/utils/locales/en.json index 9efe4c1..7c7fe76 100644 --- a/src/utils/locales/en.json +++ b/src/utils/locales/en.json @@ -4,20 +4,20 @@ "others": "Others", "roadmap": "Roadmap", "MENU": "Menu", - "orders": "Orders", + "orders": "Bookings", "orderspreview": "Orders Preview", - "deliveries": "Deliveries", - "tenants": "Tenants", + "deliveries": "Consignments", + "tenants": "Clients", "pricing": "Pricing", "customers": "Customers", - "riders": "Riders", + "riders": "Milers", "reports": "Reports", "ordersummary": "Orders Summary", "ordersdetails": "Orders Details", "riderssummary": "Riders Summary", "riderslogs": "Riders Logs", "invoice": "Invoice", - "dispatch": "Dispatch", + "dispatch": "Live Operations", "profitability": "Profitability", "Doormile": "Doormile" }