initial commit
This commit is contained in:
600
src/pages/api/api.js
Normal file
600
src/pages/api/api.js
Normal file
@@ -0,0 +1,600 @@
|
||||
import axios from 'axios';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import dayjs from 'dayjs';
|
||||
const userid = localStorage.getItem('userid');
|
||||
|
||||
// ==============================|| 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;
|
||||
};
|
||||
|
||||
// ==============================|| fetchPercentageData (orders) ||============================== //
|
||||
|
||||
export const fetchPercentageData = async (appId) => {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/orders/getordersummary/?applocationid=${appId}`);
|
||||
const details = response.data.details;
|
||||
|
||||
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()
|
||||
};
|
||||
};
|
||||
// ===================================================== || 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;
|
||||
}
|
||||
};
|
||||
// ============================================= || 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);
|
||||
}
|
||||
};
|
||||
// ==============================|| fetchorderscount (orders) ||============================== //
|
||||
|
||||
export const fetchorderscount = async ({ queryKey }) => {
|
||||
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 response = await axios.get(url);
|
||||
|
||||
return response.data.details;
|
||||
};
|
||||
|
||||
// ==============================|| fetchOrders (orders) ||============================== //
|
||||
|
||||
// export const fetchOrders = async ({ queryKey }) => {
|
||||
// const [, appId, currentStatus, searchword, startdate, enddate, page, rowsPerPage, tenantid, locationid] = queryKey;
|
||||
// const url = `${
|
||||
// process.env.REACT_APP_URL
|
||||
// }/orders/getorders/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&status=${currentStatus}&fromdate=${startdate}&todate=${enddate}&pageno=${
|
||||
// page + 1
|
||||
// }&pagesize=${rowsPerPage}&keyword=${searchword}`;
|
||||
// const response = await axios.get(url);
|
||||
// return response.data.details.map((val, i) => ({ ...val, sno: i + 1 }));
|
||||
// };
|
||||
export const fetchOrders = async ({ pageParam = 1, queryKey }) => {
|
||||
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);
|
||||
|
||||
return {
|
||||
rows: response.data.details,
|
||||
nextPage: response.data.details.length === Number(rowsPerPage) ? pageParam + 1 : undefined
|
||||
};
|
||||
};
|
||||
|
||||
// ==============================|| fetchPaymentType (orders) ||============================== //
|
||||
|
||||
export const fetchPaymentType = async () => {
|
||||
const { data } = await axios.get(`${process.env.REACT_APP_URL}/utils/getapptypes/?tag=paymentmode`);
|
||||
return data.details.map((val) => ({
|
||||
...val,
|
||||
label: val.typename
|
||||
}));
|
||||
};
|
||||
|
||||
// ==============================|| fetchRidersList (orders) ||============================== //
|
||||
|
||||
export const fetchRidersList = async ({ queryKey }) => {
|
||||
try {
|
||||
const [, appId] = queryKey; // Extract appId from queryKey
|
||||
const { data } = await axios.get(`${process.env.REACT_APP_URL}/partners/getriders/?applocationid=${appId}`);
|
||||
console.log('data', data);
|
||||
const response = data?.details
|
||||
? data?.details.map((val) => ({
|
||||
...val,
|
||||
label: `${val.firstname} ${val.lastname} | ${val.contactno}`
|
||||
}))
|
||||
: [];
|
||||
return response;
|
||||
} catch (err) {
|
||||
OpenToast(err.message, 'error', 2000);
|
||||
throw err; // 🔥 REQUIRED
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| createOptimisationDeliveries (orders) Arrange the order ||============================== //
|
||||
|
||||
export const createOptimisationDeliveries = async (deliveryData) => {
|
||||
// optimse the orders
|
||||
const response = await axios.post(`https://routes.workolik.com/api/v1/optimization/createdeliveries`, deliveryData.deliveries);
|
||||
return response.data;
|
||||
};
|
||||
// ==============================|| finalCreatedeliveries (orders) ||============================== //
|
||||
|
||||
export const finalCreatedeliveries = async (deliveryData) => {
|
||||
console.log('deliveryData', deliveryData.deliveries);
|
||||
const response = await axios.post(`https://jupiter.nearle.app/live/api/v1/deliveries/createdeliveries`, deliveryData.deliveries);
|
||||
return response.data;
|
||||
};
|
||||
// ==============================|| createAutomationDeliveries (orders) Auto rider Assign ||============================== //
|
||||
|
||||
export const createAutomationDeliveries = async (variables) => {
|
||||
console.log('variables', variables);
|
||||
|
||||
// optimse the orders and auto rider assign
|
||||
const response = await axios.post(
|
||||
variables.selectedMode.value == 1
|
||||
? `https://routes.workolik.com/api/v1/optimization/riderassign?hypertuning_params=${variables.hypertuning_params}`
|
||||
: `https://routemate.workolik.com/api/v1/optimization/riderassign?strategy=multi_trip`,
|
||||
variables.selectedMode.value == 1 ? variables.deliveries : variables.data
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// ==============================|| 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;
|
||||
};
|
||||
|
||||
// ==============================|| cancelOrder (orders) ||============================== //
|
||||
|
||||
export const cancelOrder = async (orderheaderid) => {
|
||||
const response = await axios.put(`${process.env.REACT_APP_URL}/orders/updateorder`, {
|
||||
orderheaderid: orderheaderid,
|
||||
orderstatus: 'cancelled',
|
||||
cancelled: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||
});
|
||||
|
||||
return response.data;
|
||||
};
|
||||
// ==============================|| cancelMultipleOrder (orders) ||============================== //
|
||||
|
||||
export const cancelMultipleOrder = async (orderlist) => {
|
||||
console.log('data', orderlist);
|
||||
|
||||
const data = orderlist?.map((e) => ({
|
||||
orderheaderid: e.orderheaderid,
|
||||
orderstatus: 'cancelled',
|
||||
cancelled: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||
}));
|
||||
|
||||
// Send request if needed
|
||||
const response = await axios.put(`${process.env.REACT_APP_URL}/orders/updatemultipleorders`, data);
|
||||
return response.data;
|
||||
};
|
||||
// ==============================|| fetchDeliveries (deliveries) ||============================== //
|
||||
|
||||
export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => {
|
||||
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);
|
||||
|
||||
return {
|
||||
rows: response.data.details,
|
||||
nextPage: response.data.details.length === Number(rowsPerPage) ? pageParam + 1 : undefined
|
||||
};
|
||||
};
|
||||
|
||||
// ==============================|| fetchPercentageAPI (deliveries) ||============================== //
|
||||
|
||||
export const fetchPercentageAPI = async (appId) => {
|
||||
const url = `${process.env.REACT_APP_URL}/deliveries/deliverysummary/?applocationid=${appId}`;
|
||||
const response = await axios.get(url);
|
||||
|
||||
const data = response.data.details;
|
||||
|
||||
return {
|
||||
coveredOrders: data.delivered.toString(),
|
||||
cancelledOrders: data.cancelled.toString(),
|
||||
uncoveredOrders: data.pending.toString(),
|
||||
assignedOrders: data.accepted.toString(),
|
||||
createdOrders: data.created.toString(),
|
||||
closedOrders: data.delivered.toString(),
|
||||
pickedOrders: data.picked.toString(),
|
||||
percentage1: (Math.round((data.pending / data.total) * 100) || 0).toString(),
|
||||
percentage2: (Math.round((data.accepted / data.total) * 100) || 0).toString(),
|
||||
percentage3: (Math.round((data.picked / data.total) * 100) || 0).toString(),
|
||||
percentage4: (Math.round((data.delivered / data.total) * 100) || 0).toString()
|
||||
};
|
||||
};
|
||||
|
||||
// ==============================|| fetchCountAPI (deliveries) ||============================== //
|
||||
|
||||
export const fetchCountAPI = async (appId, userid, startdate, enddate, rowsPerPage, debouncedSearch, tenantid, locationid, riderid) => {
|
||||
const url =
|
||||
appId == 0
|
||||
? `${process.env.REACT_APP_URL}/deliveries/deliverysummary/?appuserid=${userid}&fromdate=${startdate}&todate=${enddate}`
|
||||
: `${process.env.REACT_APP_URL}/deliveries/deliverysummary/?applocationid=${appId}&fromdate=${startdate}&todate=${enddate}&tenantid=${tenantid}&locationid=${locationid}&userid=${riderid}`;
|
||||
const response = await axios.get(url);
|
||||
const data = response.data.details;
|
||||
return {
|
||||
uncoveredLength: data.pending,
|
||||
assignedLength: data.accepted,
|
||||
arrivedLength: data.arrived,
|
||||
pickedLength: data.picked,
|
||||
activeLength: data.active,
|
||||
coveredLength: data.delivered,
|
||||
cancelLength: data.cancelled,
|
||||
skippedLength: data.skipped
|
||||
};
|
||||
};
|
||||
|
||||
// ==============================|| cancelDeliveryAPI (deliveries) ||============================== //
|
||||
|
||||
export const cancelDeliveryAPI = async (selectedRow, cancelFeed) => {
|
||||
const payload = {
|
||||
deliveryid: selectedRow.deliveryid,
|
||||
orderheaderid: selectedRow.orderheaderid,
|
||||
orderstatus: 'cancelled',
|
||||
canceltime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
feedback: cancelFeed
|
||||
};
|
||||
const response = await axios.put(`${process.env.REACT_APP_URL}/deliveries/updatedelivery`, payload);
|
||||
return response.data;
|
||||
};
|
||||
// ==============================|| getorderdetails (deliveries) ||============================== //
|
||||
export const getorderdetails = async (orderHeaderid) => {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/orders/getorderdetails?orderheaderid=${orderHeaderid}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// ==============================|| changeRiderAPI (deliveries) ||============================== //
|
||||
|
||||
export const changeRiderAPI = async (selectedRider, selectedRow) => {
|
||||
console.log('selectedRider', selectedRider);
|
||||
console.log('selectedRow', selectedRow);
|
||||
|
||||
return axios.put(`${process.env.REACT_APP_URL}/deliveries/updatedelivery`, {
|
||||
userid: selectedRider.userid,
|
||||
deliveryid: selectedRow.deliveryid,
|
||||
orderheaderid: selectedRow.orderheaderid,
|
||||
orderstatus: 'pending',
|
||||
assigntime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||
});
|
||||
};
|
||||
// ==============================|| updateDeliveryAPI (deliveries) ||============================== //
|
||||
|
||||
export const updateDeliveryAPI = async (orderData) => {
|
||||
return axios.put(`${process.env.REACT_APP_URL}/deliveries/updatedelivery`, orderData);
|
||||
};
|
||||
|
||||
// ==============================|| getalltenants (tenants) ||============================== //
|
||||
|
||||
export const getalltenants = async ({ queryKey }) => {
|
||||
const [, appId, 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
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
OpenToast(message);
|
||||
return null; // return null for failure
|
||||
}
|
||||
};
|
||||
// ==============================|| gettenantsummary (tenants) ||============================== //
|
||||
|
||||
export const gettenantsummary = async ({ queryKey }) => {
|
||||
const [, appId] = queryKey;
|
||||
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
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
OpenToast(message);
|
||||
return null; // return null for failure
|
||||
}
|
||||
};
|
||||
// ==============================|| getpricinglist (tenants) ||============================== //
|
||||
|
||||
export const getpricinglist = async ({ queryKey }) => {
|
||||
const [, appId] = queryKey;
|
||||
try {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/tenants/getpricinglist/?moduleid=6&applocationid=${appId}`);
|
||||
return response.data.summary; // return only data, keep it clean
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
OpenToast(message);
|
||||
return null; // return null for failure
|
||||
}
|
||||
};
|
||||
// ==============================|| getcustomersummary (customers) ||============================== //
|
||||
|
||||
export const getcustomersummary = async ({ queryKey }) => {
|
||||
const [, appId] = queryKey;
|
||||
try {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/customers/getcustomersummary?applocationid=${appId}`);
|
||||
return response.data.summary;
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
OpenToast(message);
|
||||
return null; // return null for failure
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| getallcustomers (customers) ||============================== //
|
||||
|
||||
export const getallcustomers = async ({ pageParam = 1, queryKey }) => {
|
||||
const [, appId, debouncedSearch, rowsPerPage] = queryKey;
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/customers/getallcustomers/`, {
|
||||
params: {
|
||||
applocationid: appId,
|
||||
keyword: debouncedSearch,
|
||||
pageno: pageParam,
|
||||
pagesize: rowsPerPage
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
data: response.data.details || [],
|
||||
nextPage: response.data.details?.length === rowsPerPage ? pageParam + 1 : undefined
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err.response?.data?.message || err.message || 'Something went wrong';
|
||||
|
||||
OpenToast(message);
|
||||
throw err; // IMPORTANT for React Query
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| fetchAllRiders (riders) ||============================== //
|
||||
export const fetchAllRiders = async ({ pageParam = 1, queryKey }) => {
|
||||
try {
|
||||
const [, appId, debouncedSearch, tabvalue] = queryKey;
|
||||
|
||||
const url = `${process.env.REACT_APP_URL
|
||||
}/partners/getallriders/?applocationid=${appId}&pageno=${pageParam}&pagesize=${20}&keyword=${debouncedSearch}&status=${tabvalue == 0 ? '' : 'Active'
|
||||
}`;
|
||||
const res = await axios.get(url);
|
||||
return {
|
||||
details: res.data.details,
|
||||
nextPage: res.data.details.length === 20 ? pageParam + 1 : undefined
|
||||
};
|
||||
} catch (err) {
|
||||
console.log('fetchAllRiders err', err.message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
// ==============================|| getallridersummary (riders) ||============================== //
|
||||
export const getallridersummary = async ({ queryKey }) => {
|
||||
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;
|
||||
} catch (err) {
|
||||
console.log('getallridersummary err', err.message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| fetchRiders (riders), active riders ||============================== //
|
||||
|
||||
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 [];
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| getriderstatus (riders)||============================== //
|
||||
export const getriderstatus = async () => {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/utils/getriderstatus`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
// ==============================|| getreportsummary (orders summary)||============================== //
|
||||
export const getreportsummary = async ({ queryKey }) => {
|
||||
console.log('queryKey for getreportsummary', queryKey);
|
||||
const [appId, tenantid, locationid, startdate, enddate] = queryKey;
|
||||
const response = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/deliveries/getreportsummary/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&fromdate=${startdate}&todate=${enddate}`
|
||||
);
|
||||
console.log('getreportsummary', response.data.details);
|
||||
|
||||
return response.data.details;
|
||||
};
|
||||
|
||||
// ==============================|| getreportlocationsummary (orders summary)||============================== //
|
||||
export const getreportlocationsummary = async ({ queryKey }) => {
|
||||
console.log('queryKey for getreportlocationsummary', queryKey);
|
||||
const [appId, tenantid, locationid, startdate, enddate] = queryKey;
|
||||
const response = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/deliveries/getreportlocationsummary/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&fromdate=${startdate}&todate=${enddate}`
|
||||
);
|
||||
console.log('getreportlocationsummary', response.data.details);
|
||||
|
||||
return response.data.details;
|
||||
};
|
||||
// ==============================|| fetchorderdetails (orders detail)||============================== //
|
||||
export const fetchorderdetails = async ({ queryKey }) => {
|
||||
console.log('queryKey of fetchorderdetails', queryKey);
|
||||
|
||||
const [appId, startdate, enddate, page, rowsPerPage] = queryKey;
|
||||
|
||||
const response = await axios.get(
|
||||
appId == 0
|
||||
? `${process.env.REACT_APP_URL2}/orders/getorders/?appuserid=${userid}&fromdate=${startdate}&todate=${enddate}&pageno=${page + 1
|
||||
}&pagesize=${rowsPerPage}`
|
||||
: `${process.env.REACT_APP_URL2}/orders/getorders/?fromdate=${startdate}&todate=${enddate}&applocationid=${appId}&pageno=${page}&pagesize=${rowsPerPage}`
|
||||
);
|
||||
const detailsWithSNo = response.data.details.map((item, index) => ({
|
||||
...item,
|
||||
sno: index + 1
|
||||
}));
|
||||
console.log('fetchorderdetails', detailsWithSNo);
|
||||
return detailsWithSNo;
|
||||
};
|
||||
|
||||
// ==============================|| getriderbydelivery (orders detail)||============================== //
|
||||
|
||||
export const getriderbydelivery = async (startdate, enddate, appId = 0, tenantid = 0, locationid = 0) => {
|
||||
// const [, startdate, enddate] = queryKey;
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/deliveries/getriderbydelivery/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&fromdate=${startdate}&todate=${enddate}`
|
||||
);
|
||||
return response.data.details || [];
|
||||
} catch (err) {
|
||||
console.log('getriderbydelivery', err.message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| fetchCount (orders detail)||============================== //
|
||||
|
||||
export const fetchCount = async ({ queryKey }) => {
|
||||
console.log('queryKey of fetchCount', queryKey);
|
||||
const [appId, startdate, enddate] = queryKey;
|
||||
let url =
|
||||
appId == 0
|
||||
? `${process.env.REACT_APP_URL}/deliveries/deliverysummary/?fromdate=${startdate}&todate=${enddate}`
|
||||
: `${process.env.REACT_APP_URL}/deliveries/deliverysummary/?applocationid=${appId}&fromdate=${startdate}&todate=${enddate}`;
|
||||
|
||||
const response = await axios.get(url);
|
||||
return response.data.details;
|
||||
};
|
||||
|
||||
// ==============================|| fetchRidersSummary (riders summary)||============================== //
|
||||
|
||||
export const fetchRidersSummary = async ({ queryKey }) => {
|
||||
console.log('queryKey for fetchRidersSummary', queryKey);
|
||||
const [, appId, startdate, enddate] = queryKey;
|
||||
const response = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/deliveries/getridersummary/?applocationid=${appId}&fromdate=${startdate}&todate=${enddate}`
|
||||
);
|
||||
console.log('fetchRidersSummary', response.data.details);
|
||||
|
||||
return response.data.details;
|
||||
};
|
||||
|
||||
// ==============================|| fetchLocations (orders summary))||============================== //
|
||||
export const fetchLocations = async () => {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/partners/getpartners`);
|
||||
const updatedLocations = [
|
||||
...response.data.details,
|
||||
{ partnername: 'All', partnerid: -1 } // Add your new object here
|
||||
];
|
||||
console.log('fetchLocations', updatedLocations);
|
||||
return updatedLocations;
|
||||
};
|
||||
|
||||
// ==============================|| fetchinvoiceinsight (Invoice)||============================== //
|
||||
|
||||
export const fetchinvoiceinsight = async () => {
|
||||
const insightResponse = await axios.get(`${process.env.REACT_APP_URL}/invoice/getinvoiceinsight`);
|
||||
return insightResponse.data.details;
|
||||
};
|
||||
|
||||
// ==============================|| fetchdeliverylist (Invoice)||============================== //
|
||||
|
||||
export const fetchdeliverylist = async ({ queryKey }) => {
|
||||
const [billStatus] = queryKey;
|
||||
const deliveyResponse = await axios.get(`${process.env.REACT_APP_URL}/invoice/getallinvoice/?billstatus=${billStatus}`);
|
||||
console.log('fetchdeliverylist', deliveyResponse.data.details);
|
||||
return deliveyResponse.data.details;
|
||||
};
|
||||
// ==============================|| fetchRidersLogs (RiderLogs)||============================== //
|
||||
|
||||
export const fetchRidersLogs = async ({ queryKey }) => {
|
||||
const [appId, startdate, riderSearch = ''] = queryKey;
|
||||
const riderLogsResponse = await axios.get(
|
||||
`${process.env.REACT_APP_URL2}/partners/getriderlogs/?applocationid=${appId}&fromdate=${startdate || ''}&todate=${startdate}&keyword=${riderSearch || ''
|
||||
} `
|
||||
);
|
||||
console.log('fetchRidersLogs', riderLogsResponse.data.details);
|
||||
return riderLogsResponse.data.details;
|
||||
};
|
||||
|
||||
// ==============================|| getorders (Locations)||============================== //
|
||||
// fetchOrders.js
|
||||
|
||||
export const fetchOrders1 = async ({ pageParam = 1, queryKey }) => {
|
||||
const [, tenantid, locationid, status, startdate, enddate, searchword, rowsPerPage] = queryKey;
|
||||
|
||||
const res = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/orders/tenant/getorders/?tenantid=${tenantid}&locationid=${locationid}&status=${status}&fromdate=${startdate}&todate=${enddate}&pageno=${pageParam}&pagesize=${rowsPerPage}&keyword=${searchword}`
|
||||
);
|
||||
|
||||
return {
|
||||
details: res.data.details,
|
||||
nextPage: res.data.details.length === rowsPerPage ? pageParam + 1 : undefined
|
||||
};
|
||||
};
|
||||
// ==============================|| getusers (viewProfile)||============================== //
|
||||
|
||||
export const getusers = async () => {
|
||||
try {
|
||||
const res = await axios.get(`${process.env.REACT_APP_URL}/users/getusers/?configid=9&userid=${userid}`);
|
||||
return res.data.details;
|
||||
} catch (err) {
|
||||
console.log('getusers', err.message);
|
||||
}
|
||||
};
|
||||
// ==============================|| getallriders (order)||============================== //
|
||||
|
||||
export const getallriders = async () => {
|
||||
try {
|
||||
const res = await axios.get(`${process.env.REACT_APP_URL}/partners/getallriders?partnerid=64`);
|
||||
return res.data.details;
|
||||
} catch (err) {
|
||||
console.log('getallriders', err.message);
|
||||
}
|
||||
};
|
||||
57
src/pages/auth/check-mail.js
Normal file
57
src/pages/auth/check-mail.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Box, Button, Grid, Divider, Typography, useMediaQuery } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import useAuth from 'hooks/useAuth';
|
||||
import AnimateButton from 'components/@extended/AnimateButton';
|
||||
import AuthWrapper from 'sections/auth/AuthWrapper';
|
||||
|
||||
// ================================|| CHECK MAIL ||================================ //
|
||||
|
||||
const CheckMail = () => {
|
||||
const theme = useTheme();
|
||||
const matchDownSM = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const { isLoggedIn } = useAuth();
|
||||
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||
<Typography variant="h3">Hi, Check Your Mail</Typography>
|
||||
<Typography color="secondary" sx={{ mb: 0.5, mt: 1.25 }}>
|
||||
We have sent a password recover instructions to your email.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<AnimateButton>
|
||||
<Button
|
||||
component={Link}
|
||||
to={isLoggedIn ? '/auth/login' : '/login'}
|
||||
disableElevation
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider>
|
||||
<Typography variant={matchDownSM ? 'subtitle1' : 'h5'}>Sign up with</Typography>
|
||||
</Divider>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckMail;
|
||||
29
src/pages/auth/code-verification.js
Normal file
29
src/pages/auth/code-verification.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// material-ui
|
||||
import { Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import AuthWrapper from 'sections/auth/AuthWrapper';
|
||||
import AuthCodeVerification from 'sections/auth/auth-forms/AuthCodeVerification';
|
||||
|
||||
// ================================|| CODE VERIFICATION ||================================ //
|
||||
|
||||
const CodeVerification = () => (
|
||||
<AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="h3">Enter Verification Code</Typography>
|
||||
<Typography color="secondary">We send you on mail.</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>We`ve send you code on jone. ****@company.com</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<AuthCodeVerification />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
|
||||
export default CodeVerification;
|
||||
41
src/pages/auth/forgot-password.js
Normal file
41
src/pages/auth/forgot-password.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import useAuth from 'hooks/useAuth';
|
||||
import AuthWrapper from 'sections/auth/AuthWrapper';
|
||||
import AuthForgotPassword from 'sections/auth/auth-forms/AuthForgotPassword';
|
||||
|
||||
// ================================|| FORGOT PASSWORD ||================================ //
|
||||
|
||||
const ForgotPassword = () => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||
<Typography variant="h3">Forgot Password</Typography>
|
||||
<Typography
|
||||
component={Link}
|
||||
to={isLoggedIn ? '/auth/login' : '/login'}
|
||||
variant="body1"
|
||||
sx={{ textDecoration: 'none' }}
|
||||
color="primary"
|
||||
>
|
||||
Back to Login
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<AuthForgotPassword />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPassword;
|
||||
41
src/pages/auth/login.js
Normal file
41
src/pages/auth/login.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import useAuth from 'hooks/useAuth';
|
||||
import AuthWrapper from 'sections/auth/AuthWrapper';
|
||||
import AuthLogin from 'sections/auth/auth-forms/AuthLogin';
|
||||
|
||||
// ================================|| LOGIN ||================================ //
|
||||
|
||||
const Login = () => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||
<Typography variant="h3">Login</Typography>
|
||||
<Typography
|
||||
component={Link}
|
||||
to={isLoggedIn ? '/auth/register' : '/register'}
|
||||
variant="body1"
|
||||
sx={{ textDecoration: 'none' }}
|
||||
color="primary"
|
||||
>
|
||||
Don't have an account?
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<AuthLogin />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
41
src/pages/auth/register.js
Normal file
41
src/pages/auth/register.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import useAuth from 'hooks/useAuth';
|
||||
import AuthWrapper from 'sections/auth/AuthWrapper';
|
||||
import FirebaseRegister from 'sections/auth/auth-forms/AuthRegister';
|
||||
|
||||
// ================================|| REGISTER ||================================ //
|
||||
|
||||
const Register = () => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
|
||||
return (
|
||||
<AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||
<Typography variant="h3">Sign up</Typography>
|
||||
<Typography
|
||||
component={Link}
|
||||
to={isLoggedIn ? '/auth/login' : '/login'}
|
||||
variant="body1"
|
||||
sx={{ textDecoration: 'none' }}
|
||||
color="primary"
|
||||
>
|
||||
Already have an account?
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FirebaseRegister />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Register;
|
||||
26
src/pages/auth/reset-password.js
Normal file
26
src/pages/auth/reset-password.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// material-ui
|
||||
import { Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import AuthWrapper from 'sections/auth/AuthWrapper';
|
||||
import AuthResetPassword from 'sections/auth/auth-forms/AuthResetPassword';
|
||||
|
||||
// ================================|| RESET PASSWORD ||================================ //
|
||||
|
||||
const ResetPassword = () => (
|
||||
<AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack sx={{ mb: { xs: -0.5, sm: 0.5 } }} spacing={1}>
|
||||
<Typography variant="h3">Reset Password</Typography>
|
||||
<Typography color="secondary">Please choose your new password</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<AuthResetPassword />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
|
||||
export default ResetPassword;
|
||||
24
src/pages/ctrlK.js
Normal file
24
src/pages/ctrlK.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
const CtrlK = () => {
|
||||
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) {
|
||||
textFieldRef.current.blur();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyPress);
|
||||
};
|
||||
}, [textFieldRef]);
|
||||
};
|
||||
|
||||
export default CtrlK;
|
||||
0
src/pages/demoPage.js
Normal file
0
src/pages/demoPage.js
Normal file
20
src/pages/extra-pages/sample-page.js
Normal file
20
src/pages/extra-pages/sample-page.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// material-ui
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import MainCard from 'components/MainCard';
|
||||
|
||||
// ==============================|| SAMPLE PAGE ||============================== //
|
||||
|
||||
const SamplePage = () => (
|
||||
<MainCard title="Sample Card">
|
||||
<Typography variant="body2">
|
||||
Lorem ipsum dolor sit amen, consenter nipissing eli, sed do elusion tempos incident ut laborers et doolie magna alissa. Ut enif ad
|
||||
minim venice, quin nostrum exercitation illampu laborings nisi ut liquid ex ea commons construal. Duos aube grue dolor in reprehended
|
||||
in voltage veil esse colum doolie eu fujian bulla parian. Exceptive sin ocean cuspidate non president, sunk in culpa qui officiate
|
||||
descent molls anim id est labours.
|
||||
</Typography>
|
||||
</MainCard>
|
||||
);
|
||||
|
||||
export default SamplePage;
|
||||
20
src/pages/firebase/firebase.js
Normal file
20
src/pages/firebase/firebase.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// firebase.js
|
||||
import firebase from 'firebase/compat/app';
|
||||
import 'firebase/compat/auth';
|
||||
import 'firebase/compat/firestore';
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: 'AIzaSyBkzz2Yua74Q9YpzGmUPFP94fmJQqNMIiU',
|
||||
authDomain: 'nearle-gear.firebaseapp.com',
|
||||
projectId: 'nearle-gear',
|
||||
storageBucket: 'nearle-gear.appspot.com',
|
||||
messagingSenderId: '140444764229',
|
||||
appId: '1:140444764229:web:2e60cbb7b4e26b33283b2c'
|
||||
};
|
||||
|
||||
// Initialize Firebase
|
||||
if (!firebase.apps.length) {
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
}
|
||||
|
||||
export default firebase; // Export the initialized firebase object
|
||||
56
src/pages/maintenance/404.js
Normal file
56
src/pages/maintenance/404.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// project import
|
||||
import { APP_DEFAULT_PATH } from 'config';
|
||||
|
||||
// material-ui
|
||||
import { Box, Button, Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// assets
|
||||
import error404 from 'assets/images/maintenance/Error404.png';
|
||||
import TwoCone from 'assets/images/maintenance/TwoCone.png';
|
||||
|
||||
// ==============================|| ERROR 404 - MAIN ||============================== //
|
||||
|
||||
function Error404() {
|
||||
return (
|
||||
<>
|
||||
<Grid
|
||||
container
|
||||
spacing={10}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
sx={{ minHeight: '100vh', pt: 1.5, pb: 1, overflow: 'hidden' }}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row">
|
||||
<Grid item>
|
||||
<Box sx={{ width: { xs: 250, sm: 590 }, height: { xs: 130, sm: 300 } }}>
|
||||
<img src={error404} alt="mantis" style={{ width: '100%', height: '100%' }} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item sx={{ position: 'relative' }}>
|
||||
<Box sx={{ position: 'absolute', top: 60, left: -40, width: { xs: 130, sm: 390 }, height: { xs: 115, sm: 330 } }}>
|
||||
<img src={TwoCone} alt="mantis" style={{ width: '100%', height: '100%' }} />
|
||||
</Box>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={2} justifyContent="center" alignItems="center">
|
||||
<Typography variant="h1">Page Not Found</Typography>
|
||||
<Typography color="textSecondary" align="center" sx={{ width: { xs: '73%', sm: '61%' } }}>
|
||||
The page you are looking was moved, removed, renamed, or might never exist!
|
||||
</Typography>
|
||||
<Button component={Link} to={APP_DEFAULT_PATH} variant="contained">
|
||||
Back To Home
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Error404;
|
||||
45
src/pages/maintenance/500.js
Normal file
45
src/pages/maintenance/500.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// project import
|
||||
import { APP_DEFAULT_PATH } from 'config';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { useMediaQuery, Box, Button, Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// assets
|
||||
import error500 from 'assets/images/maintenance/Error500.png';
|
||||
|
||||
// ==============================|| ERROR 500 - MAIN ||============================== //
|
||||
|
||||
function Error500() {
|
||||
const theme = useTheme();
|
||||
const matchDownSM = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container direction="column" alignItems="center" justifyContent="center" sx={{ minHeight: '100vh' }}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ width: { xs: 350, sm: 396 } }}>
|
||||
<img src={error500} alt="mantis" style={{ height: '100%', width: '100%' }} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<Typography align="center" variant={matchDownSM ? 'h2' : 'h1'}>
|
||||
Internal Server Error
|
||||
</Typography>
|
||||
<Typography color="textSecondary" variant="body2" align="center" sx={{ width: { xs: '73%', sm: '70%' }, mt: 1 }}>
|
||||
Server error 500. we fixing the problem. please try again at a later stage.
|
||||
</Typography>
|
||||
<Button component={Link} to={APP_DEFAULT_PATH} variant="contained" sx={{ textTransform: 'none', mt: 4 }}>
|
||||
Back To Home
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Error500;
|
||||
94
src/pages/maintenance/coming-soon.js
Normal file
94
src/pages/maintenance/coming-soon.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { useMediaQuery, Box, Button, Grid, Stack, TextField, Typography } from '@mui/material';
|
||||
|
||||
// third party
|
||||
import { useTimer } from 'react-timer-hook';
|
||||
|
||||
// assets
|
||||
import coming from 'assets/images/maintenance/coming-soon.png';
|
||||
import MainCard from 'components/MainCard';
|
||||
|
||||
// ==============================|| COMING SOON - MAIN ||============================== //
|
||||
|
||||
const TimerBox = ({ count, label }) => {
|
||||
const theme = useTheme();
|
||||
const matchDownSM = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
return (
|
||||
<MainCard content={false} sx={{ width: { xs: 60, sm: 80 } }}>
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<Box sx={{ py: 1.75 }}>
|
||||
<Typography variant={matchDownSM ? 'h4' : 'h2'}>{count}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ p: 0.5, bgcolor: 'secondary.lighter', width: '100%' }}>
|
||||
<Typography align="center" variant="subtitle2">
|
||||
{label}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</MainCard>
|
||||
);
|
||||
};
|
||||
|
||||
TimerBox.propTypes = {
|
||||
count: PropTypes.number,
|
||||
label: PropTypes.string
|
||||
};
|
||||
|
||||
function ComingSoon() {
|
||||
const time = new Date();
|
||||
time.setSeconds(time.getSeconds() + 3600 * 24 * 2 - 3600 * 15.5);
|
||||
|
||||
const { seconds, minutes, hours, days } = useTimer({ expiryTimestamp: time });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={4} direction="column" alignItems="center" justifyContent="center" sx={{ minHeight: '100vh', py: 2 }}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ height: { xs: 310, sm: 420 }, width: { xs: 360, sm: 'auto' } }}>
|
||||
<img src={coming} alt="mantis" style={{ height: '100%', width: '100%' }} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1} justifyContent="center" alignItems="center" sx={{ mt: -2 }}>
|
||||
<Typography align="center" variant="h1">
|
||||
Coming Soon
|
||||
</Typography>
|
||||
<Typography align="center" color="textSecondary">
|
||||
Something new is on its way
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ width: { xs: '95%', md: '40%' } }}>
|
||||
<Stack direction="row" alignItems="center" justifyContent="center" spacing={{ xs: 1, sm: 2 }}>
|
||||
<TimerBox count={days} label="day" />
|
||||
<Typography variant="h1"> : </Typography>
|
||||
<TimerBox count={hours} label="hour" />
|
||||
<Typography variant="h1"> : </Typography>
|
||||
<TimerBox count={minutes} label="min" />
|
||||
<Typography variant="h1"> : </Typography>
|
||||
<TimerBox count={seconds} label="sec" />
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sx={{ width: { xs: 380, md: '40%', lg: '30%' } }}>
|
||||
<Stack spacing={2} sx={{ mt: 2 }}>
|
||||
<Typography align="center" color="textSecondary">
|
||||
Be the first to be notified when Mantis launches.
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<TextField fullWidth placeholder="Email Address" />
|
||||
<Button variant="contained" sx={{ width: '50%' }}>
|
||||
Notify Me
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ComingSoon;
|
||||
39
src/pages/maintenance/under-construction.js
Normal file
39
src/pages/maintenance/under-construction.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// project import
|
||||
import { APP_DEFAULT_PATH } from 'config';
|
||||
|
||||
// material-ui
|
||||
import { Box, Button, Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// assets
|
||||
import construction from 'assets/images/maintenance/under-construction.svg';
|
||||
|
||||
// ==============================|| UNDER CONSTRUCTION - MAIN ||============================== //
|
||||
|
||||
function UnderConstruction() {
|
||||
return (
|
||||
<Grid container spacing={4} direction="column" alignItems="center" justifyContent="center" sx={{ minHeight: '100vh', py: 2 }}>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ width: { xs: 300, sm: 480 } }}>
|
||||
<img src={construction} alt="mantis" style={{ width: '100%', height: 'auto' }} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={2} justifyContent="center" alignItems="center">
|
||||
<Typography align="center" variant="h1">
|
||||
Under Construction
|
||||
</Typography>
|
||||
<Typography color="textSecondary" align="center" sx={{ width: '85%' }}>
|
||||
Hey! Please check out this site later. We are doing some maintenance on it right now.
|
||||
</Typography>
|
||||
<Button component={Link} to={APP_DEFAULT_PATH} variant="contained">
|
||||
Back To Home
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default UnderConstruction;
|
||||
43
src/pages/nearle/accountsettings.js
Normal file
43
src/pages/nearle/accountsettings.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Grid } from '@mui/material';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import CircularLoader from 'components/CircularLoader';
|
||||
import Loader from 'components/Loader';
|
||||
import MainCard from 'components/MainCard';
|
||||
import { getusers } from 'pages/api/api';
|
||||
|
||||
const ViewProfile = () => {
|
||||
const {
|
||||
data: userdata,
|
||||
isLoading,
|
||||
isError,
|
||||
error
|
||||
} = useQuery({
|
||||
queryKey: ['getuser'],
|
||||
queryFn: getusers
|
||||
});
|
||||
return (
|
||||
<>
|
||||
{isLoading && (
|
||||
<>
|
||||
<Loader />
|
||||
<CircularLoader />
|
||||
</>
|
||||
)}
|
||||
<MainCard>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
{userdata?.firstname}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
{' '}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
{' '}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewProfile;
|
||||
132
src/pages/nearle/clientPricing/clientPricing.js
Normal file
132
src/pages/nearle/clientPricing/clientPricing.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { Stack, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Chip } from '@mui/material';
|
||||
import MainCard from 'components/MainCard';
|
||||
import Loader from 'components/Loader';
|
||||
import TitleCard from 'components/nearle_components/TitleCard';
|
||||
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
|
||||
import { Empty } from 'antd';
|
||||
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
|
||||
|
||||
// ==============================|| Starts here ||============================== //
|
||||
const ClientsPricing = () => {
|
||||
const [appId, setAppId] = useState(0);
|
||||
const [locaName, setLocoName] = useState('');
|
||||
const [pricing, setpricing] = useState([]);
|
||||
const [isLoader, setIsloader] = useState([]);
|
||||
|
||||
// ==============================|| formatNumberToRupees ||============================== //
|
||||
function formatNumberToRupees(value) {
|
||||
return new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: 'INR',
|
||||
minimumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
function dotzerozero(value) {
|
||||
return new Intl.NumberFormat('en-IN', {
|
||||
minimumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
// ==============================|| getAllPricing ||============================== //
|
||||
const getAllPricing = async () => {
|
||||
setIsloader(true);
|
||||
try {
|
||||
const pricingres = await axios.get(`${process.env.REACT_APP_URL}/utils/getallpricing/?applocationid=${appId}`);
|
||||
console.log('pricingres', pricingres.data.details);
|
||||
setpricing(pricingres.data.details);
|
||||
setIsloader(false);
|
||||
} catch (err) {
|
||||
console.log('pricingres', err);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
getAllPricing();
|
||||
}, [appId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoader && <Loader />}
|
||||
{/* ============================================= || Title Card || ============================================= */}
|
||||
<Stack direction={'row'} justifyContent="space-between" alignItems="center" sx={{ my: 1 }}>
|
||||
<TitleCard title="Pricing" />
|
||||
<Stack sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<LocationAutocomplete
|
||||
locaName={locaName}
|
||||
setAppId={setAppId}
|
||||
setLocoName={setLocoName}
|
||||
sx={{ width: { xs: '100%', custom450: 300 }, zIndex: '100' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{/* ============================================= || table || ============================================= */}
|
||||
<MainCard content={false} sx={{ mt: 2 }}>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead sx={{ bgcolor: '#e1bee7' }}>
|
||||
<TableRow>
|
||||
<TableCell>S.No</TableCell>
|
||||
<TableCell>Location</TableCell>
|
||||
<TableCell align="center">Pricing Id</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Slab</TableCell>
|
||||
<TableCell align="center">Base Price</TableCell>
|
||||
<TableCell align="center">MinKm</TableCell>
|
||||
<TableCell align="center">Price/Km</TableCell>
|
||||
<TableCell align="center">MaxKm</TableCell>
|
||||
<TableCell>Min Orders</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoader && <OrdersTableSkeleton col={5} />}
|
||||
{pricing?.length === 0 && !isLoader ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={10} align="center">
|
||||
<Empty description={'No Pricing List'} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
pricing?.map((data, index) => (
|
||||
<TableRow key={data.pricingid || index}>
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
|
||||
<TableCell>{data.applocation}</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Chip size="small" color="info" label={data.pricingid} sx={{ width: 50 }} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell sx={{ whiteSpace: 'nowrap' }}>{data.appname}</TableCell>
|
||||
|
||||
<TableCell>{data.slab}</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Chip size="small" color="primary" label={formatNumberToRupees(data.baseprice)} sx={{ width: 100 }} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Chip size="small" color="warning" label={dotzerozero(data.minkm)} sx={{ width: 100 }} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Chip size="small" color="success" label={formatNumberToRupees(data.priceperkm)} sx={{ width: 100 }} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Chip size="small" color="error" label={dotzerozero(data.maxkm)} sx={{ width: 100 }} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{data.minorder}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</MainCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClientsPricing;
|
||||
1717
src/pages/nearle/clients/Tenants.js
Normal file
1717
src/pages/nearle/clients/Tenants.js
Normal file
File diff suppressed because it is too large
Load Diff
537
src/pages/nearle/clients/createCustomer.js
Normal file
537
src/pages/nearle/clients/createCustomer.js
Normal file
@@ -0,0 +1,537 @@
|
||||
import { React, useEffect, useState, useRef } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography, IconButton, Autocomplete } from '@mui/material';
|
||||
import MainCard from 'components/MainCard';
|
||||
import axios from 'axios';
|
||||
import Loader from 'components/Loader';
|
||||
import Geocode from 'react-geocode';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
|
||||
const CreateCustomer = () => {
|
||||
const [appId, setAppId] = useState(0);
|
||||
const locationRef = useRef(null);
|
||||
const [mobilenumber, setMobilenumber] = useState('');
|
||||
const [emailaddress, setEmailaddress] = useState('');
|
||||
const [address, setAddress] = useState('');
|
||||
const [firstname, setFirstname] = useState('');
|
||||
const [doorno, setDoorno] = useState('');
|
||||
const [landmark, setLandmark] = useState('');
|
||||
const [inputValue2, setInputValue2] = useState('');
|
||||
const [appLocaLat, setAppLocaLat] = useState();
|
||||
const [appLocaLng, setAppLocaLng] = useState();
|
||||
const [appLocaRadius, setAppLocaRadius] = useState();
|
||||
const [locaName, setLocoName] = useState('Select Location');
|
||||
const [tenantlist, setTenantlist] = useState([]);
|
||||
const [tid, setTid] = useState(0);
|
||||
const [pickCust, setPickCust] = useState({});
|
||||
const [startPoint, setStartPoint] = useState({ latitude: 0, longitude: 0 });
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize Google Maps Autocomplete
|
||||
if (inputValue2) {
|
||||
const autocompleteInput = document.getElementById('addressAuto1');
|
||||
const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, {
|
||||
// types: ['(cities)'], // You can adjust the types parameter based on your requirements
|
||||
strictBounds: true,
|
||||
bounds: new window.google.maps.Circle({
|
||||
// center: new window.google.maps.LatLng(11.0050707, 76.9509083),
|
||||
// radius: 100000
|
||||
center: new window.google.maps.LatLng(appLocaLat, appLocaLng),
|
||||
|
||||
radius: appLocaRadius * 1000
|
||||
}).getBounds()
|
||||
});
|
||||
// Event listener for autocomplete place changed
|
||||
autocomplete.addListener('place_changed', () => {
|
||||
const place = autocomplete.getPlace();
|
||||
setInputValue2(`${place.name}, ${place.formatted_address}`);
|
||||
console.log('new place', place); // Do something with the selected place
|
||||
console.log(' pick (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place
|
||||
// to trigger getDistance
|
||||
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
|
||||
setAddress(`${place.name} ${place.formatted_address}`);
|
||||
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
|
||||
const address = {
|
||||
address: `${place.name} ${place.formatted_address}`,
|
||||
street_number: '',
|
||||
route: '',
|
||||
locality: '',
|
||||
sublocality_level_1: '',
|
||||
administrative_area_level_3: '',
|
||||
administrative_area_level_1: '',
|
||||
country: '',
|
||||
postal_code: ''
|
||||
};
|
||||
place.address_components.forEach((component) => {
|
||||
component.types.forEach((type) => {
|
||||
switch (type) {
|
||||
case 'street_number':
|
||||
address.street_number = component.long_name;
|
||||
break;
|
||||
case 'route':
|
||||
address.route = component.long_name;
|
||||
break;
|
||||
case 'locality':
|
||||
address.locality = component.long_name;
|
||||
break;
|
||||
case 'sublocality_level_1':
|
||||
address.sublocality_level_1 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_3':
|
||||
address.administrative_area_level_3 = component.long_name;
|
||||
break;
|
||||
case 'administrative_area_level_1':
|
||||
address.administrative_area_level_1 = component.long_name;
|
||||
break;
|
||||
case 'country':
|
||||
address.country = component.long_name;
|
||||
break;
|
||||
case 'postal_code':
|
||||
address.postal_code = component.long_name;
|
||||
break;
|
||||
// Add more cases as needed for other types
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Use address object as per your requirements
|
||||
setPickCust({
|
||||
...pickCust,
|
||||
address: address.address,
|
||||
doorno: `${address.street_number} ${address.route}`,
|
||||
suburb: address.administrative_area_level_3,
|
||||
city: address.locality,
|
||||
state: address.administrative_area_level_1,
|
||||
postcode: address.postal_code,
|
||||
latitude: place.geometry.location.lat(),
|
||||
longitude: place.geometry.location.lng()
|
||||
});
|
||||
console.log('Pick Address:', address);
|
||||
});
|
||||
}
|
||||
}, [inputValue2]);
|
||||
// ==================================================== || getapplocations || ====================================================
|
||||
const getapplocations = async () => {
|
||||
setLoading(true);
|
||||
await axios
|
||||
.get(`${process.env.REACT_APP_URL}/utils/getapplocations/?applocationid=${appId}`)
|
||||
.then((res) => {
|
||||
console.log('getapplocations', res);
|
||||
const { latitude, longitude, radius } = res.data.details[0];
|
||||
if (res.data.status) {
|
||||
setAppLocaLat(latitude);
|
||||
setAppLocaLng(longitude);
|
||||
setAppLocaRadius(radius);
|
||||
console.log('radius', radius);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (appId) {
|
||||
getapplocations();
|
||||
}
|
||||
}, [appId]);
|
||||
|
||||
// ===================================================== || fetchtenantinfolist || =====================================================
|
||||
|
||||
const fetchtenantinfolist = async (id) => {
|
||||
setLoading(true);
|
||||
await axios
|
||||
.get(`${process.env.REACT_APP_URL}/tenants/gettenants/?applocationid=${id}&status=active`)
|
||||
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
if (res.data.status) {
|
||||
let arr = [];
|
||||
res.data.details.map((val) => {
|
||||
arr.push({
|
||||
...val,
|
||||
label: `${val.tenantname}`
|
||||
});
|
||||
});
|
||||
setTenantlist(arr);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
appId && fetchtenantinfolist(appId);
|
||||
}, [appId]);
|
||||
// ============================================= || gettenantlocations (branches) || =============================================
|
||||
const gettenantlocations = async (id) => {
|
||||
try {
|
||||
const res = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantlocations/?tenantid=${id}`);
|
||||
console.log('gettenantlocations', res.data.details);
|
||||
if (res.data.details.length == 1) {
|
||||
} else {
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('gettenantlocations', err);
|
||||
}
|
||||
};
|
||||
|
||||
const opentoast = (message) => {
|
||||
enqueueSnackbar(message, {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
};
|
||||
|
||||
const createprofile = async () => {
|
||||
let obj = {
|
||||
applocationid: +appId,
|
||||
tenantid: +tid,
|
||||
customerid: 0,
|
||||
configid: 1,
|
||||
firstname: firstname,
|
||||
dialcode: '+91',
|
||||
contactno: mobilenumber,
|
||||
email: emailaddress,
|
||||
doorno: doorno,
|
||||
address: pickCust.address,
|
||||
suburb: pickCust.suburb,
|
||||
city: pickCust.city,
|
||||
state: pickCust.state,
|
||||
postcode: pickCust.postcode,
|
||||
landmark: landmark,
|
||||
latitude: startPoint.latitude.toString(),
|
||||
longitude: startPoint.longitude.toString(),
|
||||
profileimage: '',
|
||||
devicetype: '',
|
||||
deviceid: '',
|
||||
customertoken: '',
|
||||
primaryaddress: 1
|
||||
};
|
||||
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('/nearle/customers');
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <Loader />}
|
||||
<Grid item xs={12} sx={{ mb: 2 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h3">Create Customer</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<MainCard>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={3}>
|
||||
{/* ===================================================== || Choose location || ===================================================== */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<LocationAutocomplete ref={locationRef} locaName={locaName} setAppId={setAppId} setLocoName={setLocoName} sx={{}} />
|
||||
</Grid>
|
||||
{/* ===================================================== || Choose client || ===================================================== */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Autocomplete
|
||||
fullWidth
|
||||
disabled={appId == 0}
|
||||
id="free-solo-demo"
|
||||
sx={{}}
|
||||
options={tenantlist || []}
|
||||
renderInput={(params) => <TextField {...params} label="Choose Client" focused />}
|
||||
onChange={(e, val, reason) => {
|
||||
if (val) {
|
||||
console.log('Client', val);
|
||||
gettenantlocations(val.tenantid);
|
||||
setTid(val.tenantid);
|
||||
} else {
|
||||
setClientinfo({});
|
||||
setTenantid('');
|
||||
}
|
||||
if (reason == 'clear') {
|
||||
}
|
||||
}}
|
||||
/>{' '}
|
||||
</Grid>
|
||||
|
||||
{/* ===================================================== || Name|| ===================================================== */}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-last-name">Name</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="personal-last-name"
|
||||
placeholder="Name"
|
||||
onChange={(e) => setFirstname(e.target.value)}
|
||||
value={firstname}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
{/* ===================================================== || Phone Number || ===================================================== */}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-phone">Phone Number</InputLabel>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<Select defaultValue="+1" disabled sx={{ cursor: 'not-allowed' }}>
|
||||
<MenuItem value="+1">+91</MenuItem>
|
||||
</Select>
|
||||
<TextField
|
||||
type="number"
|
||||
id="personal-phone"
|
||||
fullWidth
|
||||
placeholder="Phone Number"
|
||||
onChange={(e) => {
|
||||
if (e.target.value.toString().length <= 10) {
|
||||
setMobilenumber(e.target.value);
|
||||
}
|
||||
}}
|
||||
value={mobilenumber}
|
||||
autoComplete="off"
|
||||
// disabled
|
||||
sx={{ cursor: 'not-allowed' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
{/* ===================================================== || Email|| ===================================================== */}
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Email </InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
id="personal-email"
|
||||
placeholder="Email "
|
||||
onChange={(e) => setEmailaddress(e.target.value)}
|
||||
value={emailaddress}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
{/* ===================================================== || door no || ===================================================== */}
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Door No</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Door No"
|
||||
onChange={(e) => setDoorno(e.target.value)}
|
||||
value={doorno}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
{/* ===================================================== || Address || ===================================================== */}
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email"> Address</InputLabel>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
id="addressAuto1"
|
||||
fullWidth
|
||||
value={inputValue2}
|
||||
onChange={(e) => {
|
||||
if (appId) {
|
||||
appId && setInputValue2(e.target.value);
|
||||
} else {
|
||||
OpenToast('Select Location First', 'warning', 3000);
|
||||
}
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setInputValue2('');
|
||||
setPickCust({
|
||||
...pickCust,
|
||||
doorno: '',
|
||||
suburb: '',
|
||||
city: '',
|
||||
postcode: '',
|
||||
landmark: ''
|
||||
});
|
||||
setStartPoint({ latitude: 0, longitude: 0 });
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Location</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Location"
|
||||
onChange={(e) => setPickCust({ ...pickCust, suburb: e.target.value })}
|
||||
value={pickCust.suburb}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">City</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="personal-zipcode"
|
||||
placeholder="City"
|
||||
onChange={(e) => setPickCust({ ...pickCust, city: e.target.value })}
|
||||
value={pickCust.city}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">State</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="State"
|
||||
onChange={(e) => setPickCust({ ...pickCust, state: e.target.value })}
|
||||
value={pickCust.state}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">Post Code</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="956754"
|
||||
type="number"
|
||||
id="personal-zipcode"
|
||||
placeholder="Zipcode"
|
||||
onChange={(e) => setPickCust({ ...pickCust, postcode: e.target.value })}
|
||||
value={pickCust.postcode}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Landmark</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
placeholder="Landmark"
|
||||
onChange={(e) => setLandmark(e.target.value)}
|
||||
value={landmark}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="flex-end" alignItems="center" spacing={2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
if (appId === '') {
|
||||
opentoast('Select Applocation ');
|
||||
} else if (tid === '') {
|
||||
opentoast('Select Tenant');
|
||||
} else if (firstname === '') {
|
||||
opentoast('Enter Name');
|
||||
} else if (mobilenumber === '') {
|
||||
opentoast('Enter Mobile Number ');
|
||||
} else if (address === '') {
|
||||
opentoast('Enter Address ');
|
||||
} else if (pickCust.city === '') {
|
||||
opentoast('Enter City ');
|
||||
} else if (pickCust.state === '') {
|
||||
opentoast('Enter State ');
|
||||
} else if (pickCust.suburb === '') {
|
||||
opentoast('Enter location ');
|
||||
} else if (pickCust.postcode === '') {
|
||||
opentoast('Enter Post Code ');
|
||||
} else if (landmark === '') {
|
||||
opentoast('Enter Land Mark ');
|
||||
} else if (pickCust.latitude === '') {
|
||||
opentoast('Invalid latitude ');
|
||||
} else if (pickCust.longitude === '') {
|
||||
opentoast('Invaiid Longitude ');
|
||||
} else {
|
||||
createprofile();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateCustomer;
|
||||
604
src/pages/nearle/clients/createclient.js
Normal file
604
src/pages/nearle/clients/createclient.js
Normal file
@@ -0,0 +1,604 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Box, Button, FormLabel, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography } from '@mui/material';
|
||||
|
||||
// third-party
|
||||
// import { PatternFormat } from 'react-number-format';
|
||||
|
||||
// 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 { useNavigate } from 'react-router';
|
||||
// import { setLocationType } from 'react-geocode';
|
||||
|
||||
// const avatarImage = require.context('assets/images/users', true);
|
||||
|
||||
// styles & constant
|
||||
// const ITEM_HEIGHT = 48;
|
||||
// const ITEM_PADDING_TOP = 8;
|
||||
// const MenuProps = {
|
||||
// PaperProps: {
|
||||
// style: {
|
||||
// maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
const Createclient = () => {
|
||||
const theme = useTheme();
|
||||
// const [role, setRole] = useState('');
|
||||
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 navigate = useNavigate();
|
||||
|
||||
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'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
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 fetchprofiledetails = async (userid) => {
|
||||
if (userid) {
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios
|
||||
.get(`${process.env.REACT_APP_URL2}/tenants/getclient?id=${userid}`)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
if (res.data.message === 'Successful') {
|
||||
let res1 = res.data.details;
|
||||
setMobilenumber(res1.contactno);
|
||||
setEmailaddress(res1.primaryemail);
|
||||
setAddress(res1.address);
|
||||
setCity(res1.city);
|
||||
setZipcode(res1.postcode);
|
||||
setState(res1.state);
|
||||
setSuburb(res1.suburb);
|
||||
setLatlong({
|
||||
lat: res1.latitude,
|
||||
lng: res1.longitude
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
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 (!businessname) {
|
||||
// opentoast('Fill Business name')
|
||||
// } else if (!businessno) {
|
||||
// opentoast('Fill Registration No')
|
||||
// }
|
||||
// else
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// const [experience, setExperience] = useState('0');
|
||||
|
||||
// const handleChange = (event) => {
|
||||
// setExperience(event.target.value);
|
||||
// };
|
||||
return (
|
||||
<>
|
||||
{loading && <Loader />}
|
||||
|
||||
<Grid item xs={12} sx={{ mb: 2 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h3">Create Client</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<MainCard>
|
||||
<Grid container spacing={3}>
|
||||
{/* <Grid item xs={12} sm={4} >
|
||||
<MainCard title="Personal Information" sx={{ height: '100%' }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={2.5} alignItems="center" sx={{ m: 3 }}>
|
||||
<FormLabel
|
||||
htmlFor="change-avtar"
|
||||
sx={{
|
||||
position: 'relative',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
'&:hover .MuiBox-root': { opacity: 1 },
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Avatar alt="Avatar 1"
|
||||
src={avatar}
|
||||
sx={{ width: 76, height: 76 }} />
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
backgroundColor: theme.palette.mode === ThemeMode.DARK ? 'rgba(255, 255, 255, .75)' : 'rgba(0,0,0,.65)',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
opacity: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Stack spacing={0.5} alignItems="center">
|
||||
<CameraOutlined style={{ color: theme.palette.secondary.lighter, fontSize: '1.5rem' }} />
|
||||
<Typography sx={{ color: 'secondary.lighter' }} variant="caption">
|
||||
Upload
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</FormLabel>
|
||||
<TextField
|
||||
|
||||
type="file"
|
||||
accept="image/*"
|
||||
id="change-avtar"
|
||||
placeholder="Outlined"
|
||||
variant="outlined"
|
||||
sx={{ display: 'none' }}
|
||||
onChange={(e) => setSelectedImage(e.target.files?.[0])}
|
||||
/>
|
||||
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
|
||||
<Grid item xs={12}
|
||||
>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={12}
|
||||
>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={12}
|
||||
>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}
|
||||
|
||||
>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-role-name">Role</InputLabel>
|
||||
<TextField fullWidth
|
||||
|
||||
id="personal-role-name" placeholder="Role Name" autoFocus
|
||||
onChange={(e) => setRole(e.target.value)}
|
||||
value={role}
|
||||
autoComplete='off'
|
||||
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</Grid> */}
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
// sm={8}
|
||||
>
|
||||
<MainCard
|
||||
// title="Contact Information"
|
||||
sx={{ height: '100%' }}
|
||||
>
|
||||
<Grid container spacing={3}>
|
||||
{/* <Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-first-name">Business Name</InputLabel>
|
||||
<TextField fullWidth
|
||||
id="personal-first-name" placeholder="Business Name" autoFocus
|
||||
onChange={(e) => setBusinessname(e.target.value)}
|
||||
value={businessname}
|
||||
autoComplete='off'
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-last-name">Registration No</InputLabel>
|
||||
<TextField fullWidth
|
||||
id="personal-last-name" placeholder="Registration No"
|
||||
onChange={(e) => setBusinessno(e.target.value)}
|
||||
value={businessno}
|
||||
autoComplete='off'
|
||||
|
||||
/>
|
||||
</Stack>
|
||||
</Grid> */}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-last-name">Admin Name</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="personal-last-name"
|
||||
placeholder="Name"
|
||||
onChange={(e) => setFirstname(e.target.value)}
|
||||
value={firstname}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}></Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-phone">Phone Number</InputLabel>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<Select defaultValue="+1" disabled sx={{ cursor: 'not-allowed' }}>
|
||||
<MenuItem value="+1">+91</MenuItem>
|
||||
</Select>
|
||||
<TextField
|
||||
type="number"
|
||||
id="personal-phone"
|
||||
// format="##########"
|
||||
// mask="_"
|
||||
fullWidth
|
||||
// customInput={TextField}
|
||||
placeholder="Phone Number"
|
||||
// defaultValue="8654239581"
|
||||
// onBlur={() => { }}
|
||||
onChange={(e) => {
|
||||
if (e.target.value.toString().length <= 10) {
|
||||
setMobilenumber(e.target.value);
|
||||
}
|
||||
}}
|
||||
value={mobilenumber}
|
||||
autoComplete="off"
|
||||
// disabled
|
||||
sx={{ cursor: 'not-allowed' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Email Address</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
placeholder="Email Address"
|
||||
onChange={(e) => setEmailaddress(e.target.value)}
|
||||
value={emailaddress}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-address">Address</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
|
||||
id="personal-address"
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
inputRef={materialRef}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Suburb</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Location"
|
||||
onChange={(e) => setSuburb(e.target.value)}
|
||||
value={suburb}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">City</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="956754"
|
||||
// type='number'
|
||||
id="personal-zipcode"
|
||||
placeholder="City"
|
||||
onChange={(e) => setCity(e.target.value)}
|
||||
value={city}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">State</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="State"
|
||||
onChange={(e) => setState(e.target.value)}
|
||||
value={state}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">Post Code</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="956754"
|
||||
type="number"
|
||||
id="personal-zipcode"
|
||||
placeholder="Zipcode"
|
||||
onChange={(e) => setZipcode(e.target.value)}
|
||||
value={zipcode}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Door No</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Door No"
|
||||
onChange={(e) => setDoorno(e.target.value)}
|
||||
value={doorno}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Landmark</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
placeholder="Landmark"
|
||||
onChange={(e) => setLandmark(e.target.value)}
|
||||
value={landmark}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="flex-end" alignItems="center" spacing={2}>
|
||||
<Button variant="contained" onClick={() => createprofile()}>
|
||||
Create
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Createclient;
|
||||
693
src/pages/nearle/customers/customers.js
Normal file
693
src/pages/nearle/customers/customers.js
Normal file
@@ -0,0 +1,693 @@
|
||||
import { React, useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { Empty } from 'antd';
|
||||
import axios from 'axios';
|
||||
import { FaRegEdit } from 'react-icons/fa';
|
||||
import { RiEdit2Fill } from 'react-icons/ri';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import LoaderWithImage from 'components/nearle_components/LoaderWithImage';
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Grid,
|
||||
Typography,
|
||||
IconButton,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
Button,
|
||||
TextField,
|
||||
Autocomplete
|
||||
} from '@mui/material';
|
||||
import Geocode from 'react-geocode';
|
||||
import LocationOnIcon from '@mui/icons-material/LocationOn';
|
||||
import parse from 'autosuggest-highlight/parse';
|
||||
import { debounce } from '@mui/material/utils';
|
||||
// project imports
|
||||
import MainCard from 'components/MainCard';
|
||||
import Loader from 'components/Loader';
|
||||
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
|
||||
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
import { getallcustomers, getcustomersummary } from 'pages/api/api';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
|
||||
|
||||
// ==============================|| 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 };
|
||||
|
||||
// ==============================|| MUI TABLE - ENHANCED ||============================== //
|
||||
|
||||
export default function Customers() {
|
||||
const containerRef = useRef();
|
||||
const loadMoreRef = useRef();
|
||||
const [rowsPerPage] = useState(50);
|
||||
const [page] = useState(0);
|
||||
const theme = useTheme();
|
||||
const [appId, setAppId] = useState(0);
|
||||
const [locaName, setLocoName] = useState('All');
|
||||
const [selectedCustomer, setSelectedCustomer] = useState({}); // to edit
|
||||
const [open, setOpen] = useState(false);
|
||||
const [address, setAddress] = useState('');
|
||||
const [latlong, setLatlong] = useState({});
|
||||
const [city, setCity] = useState('');
|
||||
const [postcode, setPostcode] = useState('');
|
||||
const [state, setState] = useState('');
|
||||
const [suburb, setSuburb] = useState('');
|
||||
const [searchword, setSearchword] = useState('');
|
||||
const [debouncedSearch, setDebouncedSearch] = useState('');
|
||||
|
||||
// ==============================|| for google address ||============================== //
|
||||
const [value, setValue] = useState(null);
|
||||
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
|
||||
});
|
||||
// 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 || '');
|
||||
setPostcode(zipcode1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
setSelectedCustomer((prev) => ({
|
||||
...prev,
|
||||
city: city1 || '',
|
||||
state: state1 || '',
|
||||
postcode: zipcode1 || '',
|
||||
suburb: suburb1 || '',
|
||||
latitude: lat || '',
|
||||
longitude: lng || ''
|
||||
}));
|
||||
}
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}, [address]);
|
||||
|
||||
// useEffect(() => {
|
||||
// selectedCustomer &&
|
||||
// setLatlong({
|
||||
// lat: selectedCustomer.latitude,
|
||||
// lng: selectedCustomer.longitude
|
||||
// });
|
||||
// }, [selectedCustomer]);
|
||||
|
||||
// ==============================|| getallcustomers (customers) ||============================== //
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading: customersIsLoading,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
refetch: getallcustomersRefetch
|
||||
} = useInfiniteQuery({
|
||||
queryKey: ['getAllCustomers', appId, debouncedSearch, rowsPerPage],
|
||||
queryFn: getallcustomers,
|
||||
getNextPageParam: (lastPage) => lastPage.nextPage,
|
||||
keepPreviousData: true
|
||||
});
|
||||
const rows = data?.pages.flatMap((page) => page.data) || [];
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasNextPage) return;
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
fetchNextPage();
|
||||
}
|
||||
},
|
||||
{
|
||||
root: document.querySelector('.MuiTableContainer-root'), // 👈 or explicitly TableContainer
|
||||
rootMargin: '0px',
|
||||
threshold: 1.0
|
||||
}
|
||||
);
|
||||
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
|
||||
return () => {
|
||||
if (loadMoreRef.current) observer.unobserve(loadMoreRef.current);
|
||||
};
|
||||
}, [hasNextPage, fetchNextPage]);
|
||||
|
||||
const handleScroll = (event) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
|
||||
if (scrollTop + clientHeight >= scrollHeight - 50) {
|
||||
if (hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| getcustomersummary (customers) ||============================== //
|
||||
|
||||
const { data: pageCount, isLoading: customerSummaryIsLoading } = useQuery({
|
||||
queryKey: ['customersummary', appId],
|
||||
queryFn: getcustomersummary
|
||||
});
|
||||
useEffect(() => {
|
||||
console.log('pageCount', pageCount);
|
||||
}, [pageCount]);
|
||||
// ==============================|| updateCustomer (post)||============================== //
|
||||
const updateCustomer = async () => {
|
||||
console.log('selectedCustomer', selectedCustomer);
|
||||
|
||||
if (!selectedCustomer.firstname) {
|
||||
OpenToast('Enter Door NO', 'warning', 1500);
|
||||
} else if (!selectedCustomer.contactno) {
|
||||
OpenToast('Enter Contact Number ', 'warning', 1500);
|
||||
} else if (!selectedCustomer.address) {
|
||||
OpenToast('Enter Valid Address', 'warning', 1500);
|
||||
} else if (!selectedCustomer.suburb) {
|
||||
OpenToast('Enter Suburb', 'warning', 1500);
|
||||
} else if (!selectedCustomer.city) {
|
||||
OpenToast('Enter City ', 'warning', 1500);
|
||||
} else if (!selectedCustomer.state) {
|
||||
OpenToast('Enter State', 'warning', 1500);
|
||||
} else if (!selectedCustomer.postcode) {
|
||||
OpenToast('Enter PostCode', 'warning', 1500);
|
||||
} else if (!selectedCustomer.landmark) {
|
||||
OpenToast('Enter Landmark', 'warning', 1500);
|
||||
} else if (!selectedCustomer.latitude) {
|
||||
OpenToast('Enter Latitude', 'warning', 1500);
|
||||
} else if (!selectedCustomer.longitude) {
|
||||
OpenToast('Enter Longitude', 'warning', 1500);
|
||||
} else {
|
||||
try {
|
||||
const postUpdateResponse = await axios.put(`${process.env.REACT_APP_URL}/customers/update`, {
|
||||
customerid: selectedCustomer.customerid,
|
||||
configid: 1,
|
||||
firstname: selectedCustomer.firstname,
|
||||
applocationid: selectedCustomer.applocationid,
|
||||
profileimage: '',
|
||||
dialcode: '+91',
|
||||
contactno: selectedCustomer.contactno,
|
||||
devicetype: '',
|
||||
deviceid: '',
|
||||
customertoken: '123',
|
||||
address: selectedCustomer.address,
|
||||
suburb: suburb,
|
||||
city: city,
|
||||
state: state,
|
||||
postcode: postcode,
|
||||
landmark: selectedCustomer.landmark,
|
||||
doorno: selectedCustomer.doorno,
|
||||
latitude: selectedCustomer.latitude.toString(),
|
||||
longitude: selectedCustomer.longitude.toString()
|
||||
});
|
||||
|
||||
console.log('postUpdateResponse', postUpdateResponse);
|
||||
if (postUpdateResponse.data.status) {
|
||||
OpenToast(postUpdateResponse.data.message, 'success', 1500);
|
||||
setOpen(false);
|
||||
getallcustomersRefetch();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('postUpdate error', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{(customerSummaryIsLoading || customersIsLoading) && (
|
||||
<>
|
||||
{/* <CircularLoader /> */}
|
||||
<Loader />
|
||||
</>
|
||||
)}
|
||||
{/* <TitleCard title={'Customers'} /> */}
|
||||
<MainCard
|
||||
content={false}
|
||||
sx={{}}
|
||||
title={
|
||||
<Stack sx={{ m: 2 }}>
|
||||
<Stack
|
||||
direction={{ xs: 'column', sm: 'row' }}
|
||||
alignItems={{ xs: 'flex-start', sm: 'center' }}
|
||||
justifyContent="space-between"
|
||||
spacing={2}
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{/* Left: Title */}
|
||||
<Typography variant="h3">Customers</Typography>
|
||||
{/* Right: Controls */}
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} alignItems="center" width={{ xs: '100%', sm: 'auto' }}>
|
||||
<LocationAutocomplete
|
||||
locaName={locaName}
|
||||
setAppId={setAppId}
|
||||
setLocoName={setLocoName}
|
||||
sx={{
|
||||
width: { xs: '100%', sm: 320 },
|
||||
zIndex: 100
|
||||
}}
|
||||
/>
|
||||
|
||||
<DebounceSearchBar
|
||||
value={searchword}
|
||||
onChange={setSearchword}
|
||||
onDebouncedChange={setDebouncedSearch}
|
||||
placeholder={`Search ${pageCount?.Total || ''} Customer (ctrl+k)`}
|
||||
sx={{
|
||||
width: { xs: '100%', sm: 320 }
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<TableContainer ref={containerRef} onScroll={handleScroll} sx={{ maxHeight: 'calc(100vh - 180px)' }}>
|
||||
<Table stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ position: 'sticky !important' }}>#</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}>Name</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}>Contact</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}>Address</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}>Location</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}>Action</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{customersIsLoading && <OrdersTableSkeleton />}
|
||||
{rows?.length == 0 && !customersIsLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6}>
|
||||
<Stack width={'100%'} direction={'row'} justifyContent={'center'}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
rows?.map((row, index) => {
|
||||
return (
|
||||
<TableRow key={row.name}>
|
||||
<TableCell padding="none">{index + 1 + page * rowsPerPage}</TableCell>
|
||||
<TableCell align="left">
|
||||
<Typography align="left" variant="subtitle1">
|
||||
{row.firstname}
|
||||
</Typography>
|
||||
<Typography align="left" variant="caption" color="secondary">
|
||||
Id : {row.customerid}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Typography align="left" variant="subtitle1">
|
||||
{row.contactno}
|
||||
</Typography>
|
||||
<Typography align="left" variant="caption" color="secondary">
|
||||
{row.email}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="left">{row.address}</TableCell>
|
||||
<TableCell align="left">{row.suburb}</TableCell>
|
||||
<TableCell align="center">
|
||||
<Tooltip title={'To Edit'}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
console.log('row', row);
|
||||
setSelectedCustomer(row);
|
||||
setTimeout(() => {
|
||||
setOpen(true);
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<RiEdit2Fill
|
||||
fontSize={'large'}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
color: theme.palette.primary.main
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
)}
|
||||
{rows?.length != 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={15} rowSpan={3}>
|
||||
<div ref={loadMoreRef} style={{ height: 40, textAlign: 'center' }}>
|
||||
{isFetchingNextPage ? <LoaderWithImage /> : hasNextPage ? <LoaderWithImage /> : 'No More Orders'}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Divider />
|
||||
</MainCard>
|
||||
{/* ======================================== || Edit Dialog || ======================================== */}
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title" sx={{ bgcolor: theme.palette.primary.main, color: 'white' }}>
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={1}>
|
||||
<FaRegEdit style={{ fontSize: 25 }} /> <Typography variant="h3">Edit Customer</Typography>
|
||||
</Stack>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Grid container spacing={2} sx={{ mt: 2 }}>
|
||||
<Grid item xs={6}>
|
||||
<Typography sx={{ mb: 1 }}>Customer Name</Typography>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
defaultValue={selectedCustomer?.firstname}
|
||||
onChange={(e) => {
|
||||
setSelectedCustomer({
|
||||
...selectedCustomer,
|
||||
firstname: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography sx={{ mb: 1 }}>Contact Number</Typography>
|
||||
<Stack direction={'row'} spacing={1}>
|
||||
<TextField readonly variant="outlined" value={'+91'} sx={{ width: 60 }} />
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="text"
|
||||
value={selectedCustomer?.contactno || ''}
|
||||
inputProps={{
|
||||
maxLength: 10,
|
||||
inputMode: 'numeric', // mobile numeric keypad
|
||||
pattern: '[0-9]*'
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replace(/\D/g, ''); // allow only digits
|
||||
|
||||
setSelectedCustomer((prev) => ({
|
||||
...prev,
|
||||
contactno: value
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography sx={{ mb: 1 }}>Address</Typography>
|
||||
<Autocomplete
|
||||
id="google-map-demo"
|
||||
sx={{}}
|
||||
fullWidth
|
||||
getOptionLabel={(option) => (typeof option === 'string' ? option : option?.description || '')}
|
||||
filterOptions={(x) => x}
|
||||
options={options}
|
||||
autoComplete
|
||||
includeInputInList
|
||||
filterSelectedOptions
|
||||
value={selectedCustomer?.address}
|
||||
noOptionsText="No locations"
|
||||
onChange={(event, newValue) => {
|
||||
setOptions(newValue ? [newValue, ...options] : options);
|
||||
setValue(newValue);
|
||||
console.log('newValue', newValue || '');
|
||||
setAddress(newValue?.description);
|
||||
setSelectedCustomer({
|
||||
...selectedCustomer,
|
||||
address: newValue?.description
|
||||
});
|
||||
}}
|
||||
onInputChange={(event, newInputValue) => {
|
||||
setInputValue(newInputValue);
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} fullWidth />}
|
||||
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 (
|
||||
<li {...props}>
|
||||
<Grid container alignItems="center">
|
||||
<Grid item sx={{ display: 'flex', width: 44 }}>
|
||||
<LocationOnIcon sx={{ color: 'text.secondary' }} />
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
sx={{
|
||||
width: 'calc(100% - 44px)',
|
||||
wordWrap: 'break-word'
|
||||
}}
|
||||
>
|
||||
{parts?.map((part, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
component="span"
|
||||
sx={{
|
||||
fontWeight: part.highlight ? 'bold' : 'regular'
|
||||
}}
|
||||
>
|
||||
{part.text}
|
||||
</Box>
|
||||
))}
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{option?.structured_formatting.secondary_text}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography sx={{ mb: 1 }}>Location</Typography>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={selectedCustomer?.suburb}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSelectedCustomer((prev) => ({
|
||||
...prev,
|
||||
suburb: value
|
||||
}));
|
||||
// setSuburb(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography sx={{ mb: 1 }}>City</Typography>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={selectedCustomer.city || city || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSelectedCustomer((prev) => ({
|
||||
...prev,
|
||||
city: value
|
||||
}));
|
||||
// setCity(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography sx={{ mb: 1 }}>State</Typography>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={selectedCustomer.state || state || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSelectedCustomer((prev) => ({
|
||||
...prev,
|
||||
state: value
|
||||
}));
|
||||
// setState(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Typography sx={{ mb: 1 }}>Postcode</Typography>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={postcode == '' ? selectedCustomer.postcode : postcode}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSelectedCustomer((prev) => ({
|
||||
...prev,
|
||||
postcode: value
|
||||
}));
|
||||
// setPostcode(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography sx={{ mb: 1 }}>Landmark</Typography>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
defaultValue={selectedCustomer.landmark}
|
||||
onChange={(e) => {
|
||||
setSelectedCustomer({
|
||||
...selectedCustomer,
|
||||
landmark: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sx={{ cursor: 'not-allowed' }}>
|
||||
<Typography sx={{ mb: 1 }}>Latitude</Typography>
|
||||
<TextField variant="outlined" fullWidth value={latlong.lat} sx={{ cursor: 'not-allowed' }} />
|
||||
</Grid>
|
||||
<Grid item xs={6} sx={{ cursor: 'not-allowed' }}>
|
||||
<Typography sx={{ mb: 1 }}>Longitude</Typography>
|
||||
<TextField readonly variant="outlined" fullWidth value={latlong.lng} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ mr: 2, mb: 2 }}>
|
||||
<Button onClick={() => setOpen(false)} variant="contained" color="error" sx={{ mr: 2 }}>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
updateCustomer();
|
||||
}}
|
||||
variant="contained"
|
||||
autoFocus
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
9
src/pages/nearle/dashboard.js
Normal file
9
src/pages/nearle/dashboard.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<>
|
||||
<h1>Dashboard</h1>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
1711
src/pages/nearle/deliveries/deliveries.js
Normal file
1711
src/pages/nearle/deliveries/deliveries.js
Normal file
File diff suppressed because it is too large
Load Diff
419
src/pages/nearle/invoice/invoice.js
Normal file
419
src/pages/nearle/invoice/invoice.js
Normal file
@@ -0,0 +1,419 @@
|
||||
import React from 'react';
|
||||
import { useState } from 'react';
|
||||
import TitleCard from 'pages/titleCard';
|
||||
import HoverSocialCard from 'components/cards/statistics/HoverSocialCard';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import dayjs from 'dayjs';
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
dayjs.extend(utc);
|
||||
import { DashboardOutlined } from '@ant-design/icons';
|
||||
import { HiHandThumbDown, HiHandThumbUp } from 'react-icons/hi2';
|
||||
import LoadingIcons from 'react-loading-icons';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchinvoiceinsight, fetchdeliverylist } from 'pages/api/api';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import {
|
||||
Grid,
|
||||
Divider,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TablePagination,
|
||||
TableRow,
|
||||
Tabs,
|
||||
Tab,
|
||||
Typography,
|
||||
Box,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Stack
|
||||
} from '@mui/material';
|
||||
import Loader from 'components/Loader';
|
||||
import MainCard from 'components/MainCard';
|
||||
import { Empty } from 'antd';
|
||||
import DateFilterDialog from 'components/DateFilterDialog';
|
||||
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
|
||||
|
||||
const columns = [
|
||||
{ id: 'sno', label: 'sno' },
|
||||
{ id: 'client', label: 'client' },
|
||||
{ id: 'invoiceid', label: 'Invoice Id' },
|
||||
|
||||
{
|
||||
id: 'invoice date',
|
||||
label: 'invoice date',
|
||||
align: 'left'
|
||||
// format: (value) => value.toLocaleString("en-US"),
|
||||
},
|
||||
{
|
||||
id: 'due date',
|
||||
label: 'due date',
|
||||
align: 'left'
|
||||
// format: (value) => value.toLocaleString("en-US"),
|
||||
},
|
||||
{
|
||||
id: 'itemcount',
|
||||
label: 'Count',
|
||||
align: 'left'
|
||||
// format: (value) => typeof value === "number" && value.toFixed(2),
|
||||
},
|
||||
|
||||
{ id: 'amount', label: 'amount', align: 'right' },
|
||||
{ id: 'action', label: 'action', align: 'center' }
|
||||
];
|
||||
|
||||
// table data
|
||||
|
||||
function CustomTabPanel(props) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div role="tabpanel" hidden={value !== index} id={`simple-tabpanel-${index}`} aria-labelledby={`simple-tab-${index}`} {...other}>
|
||||
{value === index && (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography>{children}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function a11yProps(index) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`
|
||||
};
|
||||
}
|
||||
function formatNumberToRupees(value) {
|
||||
return new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: 'INR',
|
||||
minimumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
const Invoice = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [page, setPage] = React.useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = React.useState(10);
|
||||
const [value, setValue] = React.useState(0);
|
||||
const [billStatus, setBillStatus] = useState(0);
|
||||
let [isloader, setIsLoader] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(+event?.target?.value);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
/* ============================================= || fetchinvoiceinsight| ============================================= */
|
||||
const {
|
||||
data: insightdata,
|
||||
isLoading: isInsightLoading,
|
||||
isError: isInsightError,
|
||||
error: insightError
|
||||
} = useQuery({
|
||||
queryKey: ['invoiceInsight'], // Meaningful query key
|
||||
queryFn: fetchinvoiceinsight,
|
||||
refetchInterval: 300000 // Auto-fetch every 5 minutes
|
||||
});
|
||||
/* ============================================= || fetchdeliverylist| ============================================= */
|
||||
|
||||
const {
|
||||
data: deliveryList,
|
||||
isLoading: isDeliveryLoading,
|
||||
isError: isDeliveryError,
|
||||
error: deliveryError
|
||||
} = useQuery({
|
||||
queryKey: [billStatus], // Include billStatus in query key
|
||||
queryFn: fetchdeliverylist,
|
||||
refetchInterval: 300000 // Auto-fetch every 5 minutes
|
||||
});
|
||||
const isLoading = isInsightLoading || isDeliveryLoading;
|
||||
const isError = isInsightError || isDeliveryError;
|
||||
const errorMessage = insightError?.message || deliveryError?.message;
|
||||
|
||||
if (isError) {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{(isloader || isLoading) && <Loader />}
|
||||
{/* ============================================= || TitleCard || ============================================= */}
|
||||
<TitleCard title="Invoice" />
|
||||
|
||||
{/* ============================================= || Hover Social Card || ============================================= */}
|
||||
<Grid container spacing={3}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
lg={3}
|
||||
sm={6}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setValue(0);
|
||||
setBillStatus(0);
|
||||
}}
|
||||
>
|
||||
<HoverSocialCard
|
||||
primary="All"
|
||||
secondary={insightdata?.totalcount}
|
||||
iconPrimary={DashboardOutlined}
|
||||
color={theme.palette.primary.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
lg={3}
|
||||
sm={6}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setValue(1);
|
||||
setBillStatus(1);
|
||||
}}
|
||||
>
|
||||
<HoverSocialCard
|
||||
primary="0pen"
|
||||
secondary={insightdata?.pendingcount}
|
||||
iconPrimary={LoadingIcons.ThreeDots}
|
||||
color={theme.palette.error.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
lg={3}
|
||||
sm={6}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setValue(2);
|
||||
setBillStatus(2);
|
||||
}}
|
||||
>
|
||||
<HoverSocialCard
|
||||
primary="Overdue"
|
||||
secondary={insightdata?.overduecount}
|
||||
iconPrimary={HiHandThumbDown}
|
||||
color={theme.palette.warning.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
lg={3}
|
||||
sm={6}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setValue(3);
|
||||
setBillStatus(3);
|
||||
}}
|
||||
>
|
||||
<HoverSocialCard
|
||||
primary="Paid"
|
||||
secondary={insightdata?.paidcount}
|
||||
color={theme.palette.success.main}
|
||||
iconPrimary={HiHandThumbUp}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* ============================================= || Invoice Table || ============================================= */}
|
||||
<Stack sx={{ border: '1px solid', borderColor: 'bg.main', p: 3, mt: 3 }}>
|
||||
<Grid container sx={{}}>
|
||||
<Grid item xs={9} sx={{}}>
|
||||
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example" sx={{ mt: -1, mb: -2 }}>
|
||||
<Tab
|
||||
label="All"
|
||||
{...a11yProps(0)}
|
||||
onClick={() => {
|
||||
setValue(0);
|
||||
setBillStatus(0);
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Open"
|
||||
{...a11yProps(1)}
|
||||
onClick={() => {
|
||||
setValue(1);
|
||||
setBillStatus(1);
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Overdue"
|
||||
{...a11yProps(2)}
|
||||
onClick={() => {
|
||||
setValue(2);
|
||||
setBillStatus(2);
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Paid"
|
||||
{...a11yProps(3)}
|
||||
onClick={() => {
|
||||
setValue(3);
|
||||
setBillStatus(3);
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
<MainCard content={false}>
|
||||
<CustomTabPanel value={value} index={value}>
|
||||
<TableContainer
|
||||
sx={{
|
||||
// maxHeight: 430,
|
||||
// maxHeight: '55vh',
|
||||
mt: -3,
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '4px', // Width of vertical scrollbar
|
||||
height: '4px' // Height of horizontal scrollbar
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: '#65387A' // Color of the scrollbar thumb
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Table stickyHeader aria-label="sticky table" sx={{}}>
|
||||
<TableHead
|
||||
sx={{
|
||||
'& th': {
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
borderBottom: `2px solid ${theme.palette.divider} !important`
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TableRow>
|
||||
{columns.map((column) => (
|
||||
<TableCell
|
||||
sx={{
|
||||
minWidth: column.minWidth,
|
||||
position: 'sticky !important'
|
||||
}}
|
||||
key={column.id}
|
||||
align={column.align}
|
||||
>
|
||||
{column.label}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isDeliveryLoading && <OrdersTableSkeleton col={3} />}
|
||||
{deliveryList?.length == 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8}>
|
||||
<Empty />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
deliveryList?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((item, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{page * rowsPerPage + index + 1}</TableCell>
|
||||
<TableCell>
|
||||
<Typography>{item.tenantname} </Typography>
|
||||
|
||||
<Typography variant="caption" color={'secondary'}>
|
||||
{item.contactperson}{' '}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="caption">{item.invoiceno} </Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<stack direction={'column'}>
|
||||
<Typography variant="caption">{dayjs(item.transactiondate).format('DD-MM-YYYY')}</Typography>
|
||||
<br />
|
||||
<Typography variant="caption">{dayjs(item.transactiondate).utc().format('hh:mm a')}</Typography>
|
||||
</stack>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<stack direction={'column'}>
|
||||
<Typography variant="caption">{dayjs(item.duedate).format('DD-MM-YYYY')}</Typography>
|
||||
<br />
|
||||
<Typography variant="caption">{dayjs(item.duedate).utc().format('hh:mm a')}</Typography>
|
||||
</stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{' '}
|
||||
<Typography variant="caption">{item.itemcount} </Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography variant="caption">{formatNumberToRupees(item.totalamount)}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Tooltip title="Preview" placement="right">
|
||||
<IconButton
|
||||
sx={{
|
||||
'&:hover': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsLoader(true);
|
||||
console.log('selected', item);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsLoader(false);
|
||||
|
||||
navigate('/nearle/invoice/preview', {
|
||||
state: item
|
||||
});
|
||||
}, 500);
|
||||
}}
|
||||
>
|
||||
<VisibilityIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CustomTabPanel>
|
||||
|
||||
<Divider />
|
||||
{/* table pagination */}
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||
component="div"
|
||||
count={deliveryList?.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
/>
|
||||
</MainCard>
|
||||
{/* ============================================= || Date Dialog|| ============================================= */}
|
||||
<DateFilterDialog
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onSelect={(range) => {
|
||||
setStartdate(range.startDate);
|
||||
setEnddate(range.endDate);
|
||||
setDatestatus(range.label);
|
||||
console.log('Selected Date Range:', range);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Outlet />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Invoice;
|
||||
472
src/pages/nearle/invoice/invoicePreview.js
Normal file
472
src/pages/nearle/invoice/invoicePreview.js
Normal file
@@ -0,0 +1,472 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
// import nearleLogo from '../../../assets/images/nearleLogo.png';
|
||||
import logo_nearle1 from '../../../assets/images/logo-nearle1.png';
|
||||
import axios from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import Loader from 'components/Loader';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { DownloadOutlined, PrinterFilled } from '@ant-design/icons';
|
||||
import ReactToPrint, { useReactToPrint } from 'react-to-print';
|
||||
import { SearchOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
// import jsPDF from 'jspdf';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FaArrowLeft } from 'react-icons/fa6';
|
||||
import { FaIndianRupeeSign } from 'react-icons/fa6';
|
||||
|
||||
// import autoTable from 'jspdf-autotable';
|
||||
import {
|
||||
Grid,
|
||||
Button,
|
||||
Divider,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TablePagination,
|
||||
TableRow,
|
||||
Tabs,
|
||||
Tab,
|
||||
Typography,
|
||||
Box,
|
||||
OutlinedInput,
|
||||
InputAdornment,
|
||||
IconButton,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Stack,
|
||||
Chip
|
||||
} from '@mui/material';
|
||||
|
||||
const InvoicePreview = () => {
|
||||
const [selected, setselected] = useState({});
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
console.log('previewSelect', location.state);
|
||||
const componentRef = useRef(null);
|
||||
const [tabletype, settabletype] = useState(true);
|
||||
const [paydialog, setpaydialog] = useState(false);
|
||||
const [refnumber, setRefnumber] = useState('');
|
||||
const [remarks, setRemarks] = useState('');
|
||||
const theme = useTheme();
|
||||
useEffect(() => {
|
||||
setselected(location.state);
|
||||
}, []);
|
||||
|
||||
// ================================================= || formatNumberToRupees || =================================================
|
||||
|
||||
function formatNumberToRupees(value) {
|
||||
return new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: 'INR',
|
||||
minimumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
// ================================================= || updatePayment || =================================================
|
||||
|
||||
const updatePayment = async () => {
|
||||
try {
|
||||
const updateResponse = await axios.put(`${process.env.REACT_APP_URL}/invoice/updatestatus`, {
|
||||
salesid: selected.salesid,
|
||||
referenceno: refnumber,
|
||||
referencedate: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
billstatus: 2,
|
||||
paymentremarks: remarks
|
||||
});
|
||||
if (updateResponse.status) {
|
||||
enqueueSnackbar(' Updated Successfully ', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 1000
|
||||
});
|
||||
}
|
||||
console.log('updateResponse', updateResponse);
|
||||
} catch (error) {
|
||||
console.log('updateResponse', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack direction="row" justifyContent="Space-between" alignItems={'center'} spacing={2} sx={{ px: 2.5, py: 1, bgcolor: '#eeeeee' }}>
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2}>
|
||||
<Tooltip title="back">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
navigate('/nearle/invoice');
|
||||
}}
|
||||
>
|
||||
<FaArrowLeft size={'large'} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Stack alignItems={'center'}>
|
||||
<Typography variant="h3" color={'primary'}>
|
||||
Invoice Details
|
||||
</Typography>
|
||||
<Chip
|
||||
size="small"
|
||||
color="warning"
|
||||
variant="outlined"
|
||||
sx={{ bgcolor: theme.palette.warning.lighter }}
|
||||
label={`Invoice No :${'\u00a0\u00a0'}${selected.invoiceno}`}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack direction={'row'} spacing={2}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
sx={{
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.main',
|
||||
color: 'primary.contrastText'
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
setpaydialog(true);
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
<FaIndianRupeeSign />
|
||||
Update Payment
|
||||
</Button>
|
||||
<ReactToPrint
|
||||
trigger={() => (
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={<PrinterFilled />}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
sx={{
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.main',
|
||||
color: 'primary.contrastText'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Print
|
||||
</Button>
|
||||
)}
|
||||
content={() => componentRef.current}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Box sx={{ pb: 2.5, border: '1px solid #eee' }}>
|
||||
<div ref={componentRef} style={{ width: '100%' }}>
|
||||
<Box id="print" sx={{ p: 2.5 }}>
|
||||
<Box sx={{ pb: 2.5 }}>
|
||||
<Stack
|
||||
sx={{
|
||||
flexDirection: 'row',
|
||||
// bgcolor: theme.palette.primary.main,
|
||||
border: '1px solid #eee',
|
||||
px: 3
|
||||
}}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Box sx={{ pt: 0.5 }}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<img src={logo_nearle1} style={{ width: '150px', height: '50px' }} />{' '}
|
||||
</Stack>
|
||||
{/* <Typography
|
||||
sx={{ color: theme.palette.primary.main, py: 0.5 }}
|
||||
>
|
||||
{`Invoice No: ${"\u00a0\u00a0\u00a0"}${selected.invoiceno}`}
|
||||
</Typography> */}
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
color: theme.palette.primary.main
|
||||
}}
|
||||
variant="subtitle1"
|
||||
>
|
||||
Invoice No :
|
||||
</Typography>
|
||||
<Typography sx={{ color: theme.palette.primary.main }}>{`${'\u00a0\u00a0'}${selected.invoiceno}`}</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box sx={{ pt: 2.5, pb: 1.75 }}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography sx={{ pl: 4, color: theme.palette.primary.main }} variant="subtitle1">
|
||||
Date :{' '}
|
||||
</Typography>
|
||||
<Typography sx={{ color: theme.palette.primary.main }}>
|
||||
{dayjs(selected.transactiondate).format('DD-MM-YYYY')}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography
|
||||
sx={{
|
||||
pr: 2,
|
||||
overflow: 'hidden',
|
||||
color: theme.palette.primary.main
|
||||
}}
|
||||
variant="subtitle1"
|
||||
>
|
||||
Due Date :
|
||||
</Typography>
|
||||
<Typography sx={{ color: theme.palette.primary.main }}>{dayjs(selected.dueDate).format('DD-MM-YYYY')}</Typography>
|
||||
</Stack>
|
||||
{/* <Stack direction="row" justifyContent="space-between">
|
||||
<Typography
|
||||
sx={{
|
||||
pr: 2,
|
||||
overflow: "hidden",
|
||||
color: theme.palette.primary.main,
|
||||
}}
|
||||
variant="subtitle1"
|
||||
>
|
||||
Invoice No :
|
||||
</Typography>
|
||||
<Typography sx={{ color: theme.palette.primary.main }}>
|
||||
{`${"\u00a0\u00a0\u00a0"}${selected.invoiceno}`}
|
||||
</Typography>
|
||||
</Stack> */}
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box sx={{ pt: 2.5 }}>
|
||||
<Grid container spacing={2} justifyContent="space-between" direction="row">
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Box
|
||||
sx={{
|
||||
border: 1,
|
||||
minHeight: 240,
|
||||
borderColor: 'grey.200',
|
||||
borderRadius: 0.5,
|
||||
p: 2.5
|
||||
}}
|
||||
>
|
||||
<Grid container direction="row">
|
||||
<Grid item md={8}>
|
||||
<Stack spacing={2}>
|
||||
<Typography variant="h5">From:</Typography>
|
||||
<Stack sx={{ width: '100%' }}>
|
||||
<Typography variant="subtitle1">Nearle Technology Privite Limited.</Typography>
|
||||
<Typography color="secondary">
|
||||
424, 4<sup>th</sup>floor,
|
||||
</Typography>
|
||||
<Typography color="secondary">Red rose towers,</Typography>
|
||||
<Typography color="secondary">DB Road, RS Puram,</Typography>
|
||||
<Typography color="secondary">641002.</Typography>
|
||||
<Typography color="secondary">care@nearle.in</Typography>
|
||||
<Typography color="secondary">9047968666</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Box
|
||||
sx={{
|
||||
border: 1,
|
||||
minHeight: 240,
|
||||
borderColor: 'grey.200',
|
||||
borderRadius: 0.5,
|
||||
p: 2.5
|
||||
}}
|
||||
>
|
||||
<Grid container direction="row">
|
||||
<Grid item md={8}>
|
||||
<Stack spacing={2}>
|
||||
<Typography variant="h5">To:</Typography>
|
||||
<Stack sx={{ width: '100%' }}>
|
||||
<Typography variant="subtitle1">{selected.tenantname}</Typography>
|
||||
<Typography color="secondary">{selected.address}</Typography>
|
||||
<Typography color="secondary">{selected.suburb}</Typography>
|
||||
<Typography color="secondary">{selected.city}</Typography>
|
||||
<Typography color="secondary">{selected.state}</Typography>{' '}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>S.No</TableCell>
|
||||
<TableCell>Particulars</TableCell>
|
||||
<TableCell>Unit</TableCell>
|
||||
<TableCell>Quantity</TableCell>
|
||||
<TableCell align="right">Rate</TableCell>
|
||||
{/* {selected && selected.pricingtypeid === 73 && ( */}
|
||||
<TableCell align="right">Other Charges</TableCell>
|
||||
{/* )} */}
|
||||
<TableCell align="right">Amount</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
{selected.tenantsalesdetails && (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>1</TableCell>
|
||||
<TableCell>
|
||||
<Typography>
|
||||
{`Invoice from ${dayjs(selected.tenantsalesdetails[0].fromdate).format('DD-MM-YYYY')} to ${dayjs(
|
||||
selected.tenantsalesdetails[0].todate
|
||||
).format('DD-MM-YYYY')}`}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography>{selected.tenantsalesdetails[0].pricingtype}</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Typography>{`${selected.tenantsalesdetails[0].quantity.toFixed(2)} km`}</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography align="right">{`₹ ${selected.tenantsalesdetails[0].baserate.toFixed(2)}`}</Typography>
|
||||
</TableCell>
|
||||
{/* {selected.tenantsalesdetails[0].pricingtypeid == 73 && ( */}
|
||||
<TableCell align="right">
|
||||
<Typography>{`₹ ${selected.tenantsalesdetails[0].othercharges}.00`}</Typography>
|
||||
</TableCell>
|
||||
{/* )} */}
|
||||
<TableCell align="right">
|
||||
<Typography>{`₹ ${selected.tenantsalesdetails[0].amount}.00`}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Divider />
|
||||
<Box sx={{ p: 2.5 }}>
|
||||
<Grid container direction="row" justifyContent="flex-end">
|
||||
<Grid item md={4}>
|
||||
<Stack spacing={2}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography color="secondary">Sub Total:</Typography>
|
||||
<Typography variant="h6">{formatNumberToRupees(selected.salesamount)}</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography color="secondary">Discount:</Typography>
|
||||
<Typography variant="h6" color={theme.palette.error.main}>
|
||||
- {formatNumberToRupees(selected.discountamt)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography color={theme.palette.grey[500]}>Tax:</Typography>
|
||||
<Typography variant="h6" color={theme.palette.success.main}>
|
||||
+ {formatNumberToRupees(selected.taxamount)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography sx={{ pr: 2 }} variant="subtitle1">
|
||||
Grand Total:
|
||||
</Typography>
|
||||
<Typography variant="h6">{formatNumberToRupees(Math.round(selected.totalamount))}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box sx={{ p: 2.5 }}>
|
||||
<Typography>Notes: {selected.remarks}</Typography>
|
||||
</Box>
|
||||
<Divider />
|
||||
</div>
|
||||
</Box>
|
||||
{/* ================================================= || updatePayment Dialog || ================================================= */}
|
||||
<Dialog
|
||||
open={paydialog}
|
||||
onClose={() => {
|
||||
setpaydialog(false);
|
||||
}}
|
||||
maxWidth={'sm'}
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle sx={{ bgcolor: theme.palette.primary.main }}>
|
||||
<Stack direction={'row'} spacing={1}>
|
||||
<Typography variant="h2" sx={{ color: 'white' }}>
|
||||
₹
|
||||
</Typography>
|
||||
<Typography variant="h3" sx={{ color: 'white' }}>
|
||||
Update Payment
|
||||
</Typography>
|
||||
</Stack>
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<Stack spacing={1} sx={{ mb: 2 }}>
|
||||
<Typography>Reference No</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
placeholder="Enter Reference Number"
|
||||
sx={{ width: '100%' }}
|
||||
onChange={(e) => {
|
||||
setRefnumber(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack spacing={2} sx={{ mb: 2 }}>
|
||||
<Typography>Remarks</Typography>
|
||||
<TextField
|
||||
multiline
|
||||
required
|
||||
placeholder="Enter Remarks"
|
||||
sx={{ width: '100%' }}
|
||||
onChange={(e) => {
|
||||
setRemarks(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.main',
|
||||
color: 'primary.contrastText'
|
||||
},
|
||||
m: 2
|
||||
}}
|
||||
onClick={() => {
|
||||
setpaydialog(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
disabled={refnumber == '' || remarks == ''}
|
||||
sx={{
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.main',
|
||||
color: 'primary.contrastText'
|
||||
},
|
||||
m: 2
|
||||
}}
|
||||
onClick={() => {
|
||||
setpaydialog(false);
|
||||
updatePayment();
|
||||
navigate('/nearle/invoice');
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvoicePreview;
|
||||
397
src/pages/nearle/login.js
Normal file
397
src/pages/nearle/login.js
Normal file
@@ -0,0 +1,397 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { enqueueSnackbar, closeSnackbar } from 'notistack';
|
||||
import AnimateButton from 'components/@extended/AnimateButton';
|
||||
import OtpInput from 'react18-input-otp';
|
||||
|
||||
import { Box, Card, CardContent, Stack, TextField, Button, Typography, Link, FormLabel, IconButton, InputAdornment } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import axios from 'axios';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Loader from 'components/Loader';
|
||||
import logo from 'assets/images/logo-nearle1.png';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import { closeGlobalToast, GlobalToast } from 'components/nearle_components/GlobalToast';
|
||||
import Visibility from '@mui/icons-material/Visibility';
|
||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
import { setLoginUser } from 'store/reducers/loginUserSlice';
|
||||
|
||||
const Login = () => {
|
||||
const dispatch = useDispatch();
|
||||
const fcmtoken = useSelector((state) => state.fcm);
|
||||
const permission = useSelector((state) => state.fcm.permission);
|
||||
const theme = useTheme();
|
||||
const [loading, setLoading] = useState(false);
|
||||
let navigate = useNavigate();
|
||||
const [otp, setOtp] = useState('');
|
||||
const [currentotp, setCurrentotp] = useState('');
|
||||
const [userinfo, setUserinfo] = useState({});
|
||||
const [username, setUsername] = useState('');
|
||||
const [passwordStatus, setPasswordStatus] = useState(0);
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [isPassword, setIspassword] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const [userid, setUserid] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem('firstname')) {
|
||||
navigate('/nearle/orders');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loginsend = async () => {
|
||||
setLoading(true);
|
||||
|
||||
if (!username) {
|
||||
opentoast('Fill All required fields');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(`https://jupiter.nearle.app/live/api/v1/users/console/login`, {
|
||||
authname: username,
|
||||
configid: 9, // 9 -> config id for nearle console admin
|
||||
userfcmtoken: fcmtoken?.token,
|
||||
password
|
||||
});
|
||||
// user not found
|
||||
if (res.data.code == 409 && !res.data.status) {
|
||||
OpenToast(res.data.message, 'error', 3000);
|
||||
}
|
||||
// user not activated
|
||||
else if (res.data.code == 403) {
|
||||
OpenToast(res.data.message, 'warning', 3000);
|
||||
}
|
||||
//user found, no password, setup password
|
||||
else if (res.data.code == 409 && res.data.status) {
|
||||
setPasswordStatus(1); // for password and confirm password ui
|
||||
setUserid(res.data.details.userid);
|
||||
OpenToast('User Found', 'success', 3000);
|
||||
OpenToast(res.data.message, 'success', 3000);
|
||||
}
|
||||
//user found, incorrect password
|
||||
else if (res.data.code == 401 && !res.data.status) {
|
||||
OpenToast(res.data.message, 'error', 3000);
|
||||
}
|
||||
//user found, enter password
|
||||
else if (res.data.code == 401 && res.data.status) {
|
||||
OpenToast(res.data.message, 'success', 3000);
|
||||
fetchAppLocations(res.data.userid);
|
||||
setPasswordStatus(2);
|
||||
}
|
||||
// user found, correct password
|
||||
else if (res.data.code == 200 && res.data.status) {
|
||||
OpenToast(res.data.message, 'success', 1000);
|
||||
setUserinfo(res.data.details);
|
||||
const userinfo = res.data.details;
|
||||
dispatch(setLoginUser(userinfo));
|
||||
localStorage.setItem('firstname', userinfo.firstname);
|
||||
localStorage.setItem('authname', userinfo.authname);
|
||||
localStorage.setItem('roleid', userinfo.roleid);
|
||||
localStorage.setItem('tenantid', userinfo.tenantid);
|
||||
localStorage.setItem('partnerid', userinfo.partnerid);
|
||||
localStorage.setItem('applocationid', userinfo.applocationid);
|
||||
localStorage.setItem('userid', userinfo.userid);
|
||||
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
|
||||
fetchAppLocations(userinfo.userid);
|
||||
navigate('/nearle/orders');
|
||||
} else {
|
||||
OpenToast(res.data.message, 'error', 3000);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
OpenToast(err.message, 'error', 5000);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const loginsuccessful = () => {
|
||||
localStorage.setItem('firstname', userinfo.firstname);
|
||||
localStorage.setItem('authname', userinfo.authname);
|
||||
localStorage.setItem('roleid', userinfo.roleid);
|
||||
localStorage.setItem('tenantid', userinfo.tenantid);
|
||||
localStorage.setItem('partnerid', userinfo.partnerid);
|
||||
localStorage.setItem('applocationid', userinfo.applocationid);
|
||||
localStorage.setItem('userid', userinfo.userid);
|
||||
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
|
||||
closeGlobalToast(); // to close the pin snackbar
|
||||
|
||||
navigate('/nearle/orders');
|
||||
};
|
||||
|
||||
const opentoast = (message) => {
|
||||
enqueueSnackbar(message, {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 1500
|
||||
});
|
||||
};
|
||||
|
||||
const fetchAppLocations = async (id) => {
|
||||
const response = await axios.get(`${process.env.REACT_APP_URL}/partners/getlocations/?userid=${id}`);
|
||||
const updatedLocations = [...response.data.details, { locationname: 'All', applocationid: 0 }];
|
||||
localStorage.setItem('applocations', JSON.stringify(updatedLocations));
|
||||
};
|
||||
const updateUser = async () => {
|
||||
const response = await axios.put(`${process.env.REACT_APP_URL2}/users/update`, {
|
||||
userid,
|
||||
password
|
||||
});
|
||||
if (response.data.status) {
|
||||
OpenToast(response.data.message, 'success', 3000);
|
||||
OpenToast('Enter Password to Login', 'success', 3000);
|
||||
setPasswordStatus(2);
|
||||
setPassword('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ height: '100vh', position: 'relative' }}>
|
||||
{loading && <Loader />}
|
||||
<Stack
|
||||
sx={{
|
||||
minHeight: '100vh',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
px: 2
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxWidth: 420,
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
boxShadow: 'none',
|
||||
p: { xs: 2, sm: 3 }
|
||||
}}
|
||||
>
|
||||
{/* Logo */}
|
||||
<Stack alignItems="center" mb={2}>
|
||||
<img src={logo} alt="loginpagelogo" />
|
||||
</Stack>
|
||||
|
||||
{/* Title */}
|
||||
<Typography variant="h3" textAlign="start" mb={3}>
|
||||
Login
|
||||
</Typography>
|
||||
|
||||
<CardContent sx={{ p: 0 }}>
|
||||
<form
|
||||
noValidate
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (passwordStatus == 0) {
|
||||
loginsend();
|
||||
} else if (passwordStatus == 1) {
|
||||
if (!password || !confirmPassword || password != confirmPassword) {
|
||||
OpenToast('Check Password', 'warning', 3000);
|
||||
} else {
|
||||
updateUser();
|
||||
}
|
||||
} else if (passwordStatus == 2) {
|
||||
if (!password) {
|
||||
OpenToast('Invalid Password', 'warning', 3000);
|
||||
}
|
||||
loginsend();
|
||||
}
|
||||
// if (currentotp) {
|
||||
// if (currentotp == otp) {
|
||||
// loginsuccessful();
|
||||
// fetchAppLocations();
|
||||
// } else {
|
||||
// opentoast('Invalid pin');
|
||||
// }
|
||||
// }
|
||||
}}
|
||||
>
|
||||
<Stack spacing={3}>
|
||||
{/* Email */}
|
||||
<TextField
|
||||
autoFocus
|
||||
fullWidth
|
||||
label="E-mail Address"
|
||||
variant="outlined"
|
||||
autoComplete="email"
|
||||
required
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value.toLocaleLowerCase())}
|
||||
InputProps={{ readOnly: passwordStatus }}
|
||||
/>
|
||||
{/* Setup Password */}
|
||||
{passwordStatus == 1 && (
|
||||
<Stack display={'flex'} flexDirection={'column'} spacing={3}>
|
||||
<Typography variant="h4" textAlign="start" mb={3}>
|
||||
Setup Password
|
||||
</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
fullWidth
|
||||
label="Enter New Password"
|
||||
variant="outlined"
|
||||
required
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => setShowPassword((prev) => !prev)} edge="end">
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
error={confirmPassword !== '' && password !== confirmPassword}
|
||||
fullWidth
|
||||
label="Re-Enter Password"
|
||||
variant="outlined"
|
||||
required
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => setShowConfirmPassword((prev) => !prev)} edge="end">
|
||||
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{/* Enter Password */}
|
||||
{passwordStatus == 2 && (
|
||||
<Stack display={'flex'} flexDirection={'column'} spacing={3}>
|
||||
<Typography variant="h4" textAlign="start" mb={3}>
|
||||
Enter Password
|
||||
</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
fullWidth
|
||||
label="Enter Password"
|
||||
variant="outlined"
|
||||
required
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => setShowPassword((prev) => !prev)} edge="end">
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* OTP */}
|
||||
{isPassword && (
|
||||
<Stack spacing={1.5}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<FormLabel>Enter Password</FormLabel>
|
||||
<Link
|
||||
variant="body2"
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setOtp('');
|
||||
loginsend();
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</Link>
|
||||
</Stack>
|
||||
|
||||
{/* <OtpInput
|
||||
shouldAutoFocus
|
||||
value={otp}
|
||||
onChange={(otp) => setOtp(otp)}
|
||||
numInputs={4}
|
||||
containerStyle={{ justifyContent: 'space-between' }}
|
||||
inputStyle={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
border: `1px solid ${borderColor}`,
|
||||
fontSize: 18
|
||||
}}
|
||||
focusStyle={{
|
||||
outline: 'none',
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
boxShadow: theme.customShadows.primary
|
||||
}}
|
||||
/> */}
|
||||
<TextField type="passowrd" value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</Stack>
|
||||
)}
|
||||
{/* Submit */}
|
||||
<AnimateButton>
|
||||
<Button fullWidth size="large" type="submit" variant="contained" color="primary">
|
||||
Continue
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Stack>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Stack>
|
||||
{/* footer */}
|
||||
<Stack
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 10,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'end',
|
||||
gap: 2,
|
||||
p: 2
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
color="secondary"
|
||||
component={Link}
|
||||
href="https://nearle.in"
|
||||
target="_blank"
|
||||
sx={{ display: 'flex' }}
|
||||
>
|
||||
© All rights reserved
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
color="secondary"
|
||||
component={Link}
|
||||
href="https://nearle.in/terms"
|
||||
target="_blank"
|
||||
sx={{ display: 'flex' }}
|
||||
>
|
||||
Terms and Conditions
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
color="secondary"
|
||||
component={Link}
|
||||
href="https://nearle.in/privacy"
|
||||
target="_blank"
|
||||
sx={{ display: 'flex' }}
|
||||
>
|
||||
Privacy Policy
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
513
src/pages/nearle/login1.js
Normal file
513
src/pages/nearle/login1.js
Normal file
@@ -0,0 +1,513 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
// import AuthWrapper from 'sections/auth/AuthWrapper';
|
||||
import {
|
||||
Box,
|
||||
Grid,
|
||||
Card,
|
||||
CardContent,
|
||||
// CardHeader,
|
||||
Stack,
|
||||
// Divider,
|
||||
// InputLabel,
|
||||
// OutlinedInput,
|
||||
TextField,
|
||||
Button,
|
||||
Typography,
|
||||
CardHeader,
|
||||
Container,
|
||||
Link
|
||||
} from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import AnimateButton from 'components/@extended/AnimateButton';
|
||||
|
||||
import logo from 'assets/images/logo-nearle1.png';
|
||||
|
||||
import axios from 'axios';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
// import { openSnackbar } from 'store/reducers/snackbar';
|
||||
// import { useDispatch } from 'react-redux';
|
||||
import Loader from 'components/Loader';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
|
||||
const Login = () => {
|
||||
const theme = useTheme();
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [alertmessage, setAlertmessage] = useState('');
|
||||
const [checkusername, setCheckusername] = useState(false);
|
||||
// const [toast, setToast] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
let navigate = useNavigate();
|
||||
// let dispatch = useDispatch();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
// let loginuserid = useSelector((state)=>state.logininfo);
|
||||
|
||||
// useEffect(() => {
|
||||
|
||||
// if (alertmessage) {
|
||||
// dispatch(
|
||||
// openSnackbar({
|
||||
// open: true,
|
||||
// message: alertmessage,
|
||||
// variant: 'alert',
|
||||
// anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
// alert: {
|
||||
// // variant:'info',
|
||||
// color: 'error',
|
||||
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
// }, [toast])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
localStorage.getItem('authname')
|
||||
// || localStorage.getItem("appuserid")
|
||||
) {
|
||||
navigate('/deliveries');
|
||||
}
|
||||
|
||||
// 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((err) => {
|
||||
// if (err.response.data.message === 'No user found') {
|
||||
|
||||
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');
|
||||
setAlertmessage('Fill All required fields');
|
||||
opentoast('Fill All required fields');
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// const handleClose = () => {
|
||||
// setToast(false)
|
||||
// }
|
||||
const opentoast = (message) => {
|
||||
// setToast(true)
|
||||
|
||||
// setTimeout(() => {
|
||||
// // handleClose();
|
||||
// setToast(false)
|
||||
// }, 2000);
|
||||
enqueueSnackbar(message, {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <AuthWrapper> */}
|
||||
<Box sx={{ minHeight: '100vh' }}>
|
||||
{loading && <Loader />}
|
||||
{/* <AuthBackground /> */}
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-start"
|
||||
sx={{
|
||||
minHeight: '100vh'
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
// sx={{ ml: 3, mt: 3 }}
|
||||
sx={{ ml: 3, mt: 1 }}
|
||||
>
|
||||
<img src={logo} alt="legendary" width="200px" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
container
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
// sx={{ minHeight: { xs: 'calc(100vh - 210px)', sm: 'calc(100vh - 134px)', md: 'calc(100vh - 112px)' } }}
|
||||
sx={{ minHeight: { xs: 'calc(100vh - 210px)', sm: 'calc(100vh - 134px)', md: 'calc(100vh - 140px)' } }}
|
||||
>
|
||||
<Grid item>
|
||||
{/* <AuthCard>{children}</AuthCard> */}
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: { xs: 400, lg: 475 },
|
||||
margin: { xs: 2.5, md: 3 },
|
||||
'& > *': {
|
||||
flexGrow: 1,
|
||||
flexBasis: '50%'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
sx={{
|
||||
position: 'relative',
|
||||
border: '1px solid',
|
||||
borderRadius: 1,
|
||||
borderColor: theme.palette.divider,
|
||||
boxShadow: 'inherit',
|
||||
p: 2,
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{/* <CardHeader title={<Typography variant="h4">Login</Typography>} /> */}
|
||||
{/* <Divider sx={{ borderStyle: 'dashed' }} /> */}
|
||||
{/* <h1>eee</h1> */}
|
||||
{/* <CardHeader> */}
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="flex-start" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||
<CardHeader title={<Typography variant="h3">Login</Typography>} />
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
{/* <Grid item xs={12}>
|
||||
<AuthLogin isDemo={isLoggedIn} />
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
<CardContent>
|
||||
<form
|
||||
noValidate
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
{/* <Stack spacing={1}> */}
|
||||
{/* <InputLabel htmlFor="email-login">Email Address</InputLabel>
|
||||
<OutlinedInput
|
||||
id="email-login"
|
||||
type="email"
|
||||
value={values.email}
|
||||
name="email"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter email address"
|
||||
fullWidth
|
||||
id="username1"
|
||||
label="E-mail Address"
|
||||
variant="outlined"
|
||||
autoComplete='email'
|
||||
required
|
||||
onChange={usernamecheck}
|
||||
error={checkusername}
|
||||
error={Boolean(touched.email && errors.email)}
|
||||
/>
|
||||
{touched.email && errors.email && (
|
||||
<FormHelperText error id="standard-weight-helper-text-email-login">
|
||||
{errors.email}
|
||||
</FormHelperText>
|
||||
)} */}
|
||||
<TextField
|
||||
margin="normal"
|
||||
fullWidth
|
||||
id="username1"
|
||||
label="E-mail Address"
|
||||
variant="outlined"
|
||||
autoComplete="email"
|
||||
required
|
||||
onChange={usernamecheck}
|
||||
error={checkusername}
|
||||
/>
|
||||
<TextField
|
||||
margin="normal"
|
||||
fullWidth
|
||||
required
|
||||
autoComplete="current-password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type="password"
|
||||
id="password1"
|
||||
label="Password"
|
||||
variant="outlined"
|
||||
/>
|
||||
{/* </Stack> */}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{/* <Stack spacing={0}> */}
|
||||
{/* <InputLabel htmlFor="password-login">Password</InputLabel> */}
|
||||
{/* <OutlinedInput
|
||||
fullWidth
|
||||
// error={Boolean(touched.password && errors.password)}
|
||||
// id="-password-login"
|
||||
// type={showPassword ? 'text' : 'password'}
|
||||
// value={values.password}
|
||||
// name="password"
|
||||
// onBlur={handleBlur}
|
||||
// onChange={handleChange}
|
||||
// endAdornment={
|
||||
// <InputAdornment position="end">
|
||||
// <IconButton
|
||||
// aria-label="toggle password visibility"
|
||||
// onClick={handleClickShowPassword}
|
||||
// onMouseDown={handleMouseDownPassword}
|
||||
// edge="end"
|
||||
// color="secondary"
|
||||
// >
|
||||
// {showPassword ? <EyeOutlined /> : <EyeInvisibleOutlined />}
|
||||
// </IconButton>
|
||||
// </InputAdornment>
|
||||
// }
|
||||
placeholder="Enter password"
|
||||
// margin="normal"
|
||||
// fullWidth
|
||||
required
|
||||
autoComplete="current-password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type='password' id="password1"
|
||||
/> */}
|
||||
{/* {touched.password && errors.password && (
|
||||
<FormHelperText error id="standard-weight-helper-text-password-login">
|
||||
{errors.password}
|
||||
</FormHelperText>
|
||||
)} */}
|
||||
|
||||
{/* </Stack> */}
|
||||
<Link href="#" variant="h6">
|
||||
Forgot password?
|
||||
</Link>
|
||||
</Grid>
|
||||
|
||||
{/* <Grid item xs={12} sx={{ mt: -1 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onChange={(event) => setChecked(event.target.checked)}
|
||||
name="checked"
|
||||
color="primary"
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label={<Typography variant="h6">Keep me sign in</Typography>}
|
||||
/>
|
||||
<Link variant="h6" component={RouterLink} to={isDemo ? '/auth/forgot-password' : '/forgot-password'} color="text.primary">
|
||||
Forgot Password?
|
||||
</Link>
|
||||
</Stack>
|
||||
</Grid> */}
|
||||
{/* {errors.submit && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{errors.submit}</FormHelperText>
|
||||
</Grid>
|
||||
)} */}
|
||||
<Grid item xs={12}>
|
||||
{/* <AnimateButton> */}
|
||||
<AnimateButton>
|
||||
<Button
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
loginsend();
|
||||
// navigate('/dashboard')
|
||||
}}
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
{/* </AnimateButton> */}
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* </Grid> */}
|
||||
</form>
|
||||
</CardContent>
|
||||
{/* </CardHeader> */}
|
||||
</Card>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
// sx={{ m: 3, mt: 1 }}
|
||||
sx={{ mb: 1 }}
|
||||
>
|
||||
{/* <AuthFooter /> */}
|
||||
|
||||
<Container maxWidth="xl">
|
||||
<Stack
|
||||
direction={{ sx: 'column', md: 'row' }}
|
||||
justifyContent={{ sx: 'center', md: 'space-between' }}
|
||||
spacing={2}
|
||||
// textAlign={{ sx: 'center', md: 'inherit' }}
|
||||
|
||||
alignItems={{ sx: 'center', md: 'inherit' }}
|
||||
width="100%"
|
||||
>
|
||||
<Stack direction="row" justifyContent="center" spacing={1}>
|
||||
<Typography variant="subtitle2" color="secondary" component="span" sx={{ display: 'flex' }}>
|
||||
© All rights reserved
|
||||
{/* <Typography variant="subtitle2" href="#mantis-privacy" target="_blank" underline="hover" sx={{ml:1}}>Privacy Policy</Typography> */}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction={{ sx: 'column', md: 'row' }}
|
||||
spacing={{ sx: 1, md: 3 }}
|
||||
textAlign={{ sx: 'center', md: 'inherit' }}
|
||||
alignItems={{ sx: 'center', md: 'inherit' }}
|
||||
// width='100%'
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
color="secondary"
|
||||
component={Link}
|
||||
href="#"
|
||||
// target="_blank"
|
||||
underline="hover"
|
||||
textAlign="center"
|
||||
>
|
||||
Terms and Conditions
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
color="secondary"
|
||||
component={Link}
|
||||
href="#"
|
||||
// target="_blank"
|
||||
underline="hover"
|
||||
textAlign="center"
|
||||
>
|
||||
Privacy Policy
|
||||
</Typography>
|
||||
{/* <Typography
|
||||
variant="subtitle2"
|
||||
color="secondary"
|
||||
component={Link}
|
||||
href="#"
|
||||
// target="_blank"
|
||||
underline="hover"
|
||||
textAlign='center'
|
||||
>
|
||||
CA Privacy Notice
|
||||
</Typography> */}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
{/* </AuthWrapper> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login1;
|
||||
623
src/pages/nearle/orders/OrdersPreview.js
Normal file
623
src/pages/nearle/orders/OrdersPreview.js
Normal file
@@ -0,0 +1,623 @@
|
||||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
Chip,
|
||||
Divider,
|
||||
Grid,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Backdrop,
|
||||
IconButton
|
||||
} from '@mui/material';
|
||||
import React, { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import dayjs from 'dayjs';
|
||||
import MainCard from 'components/MainCard';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { fetchPaymentType, fetchRidersList, finalCreatedeliveries, notifyRider } from 'pages/api/api';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import Loader from 'components/Loader';
|
||||
import CircularLoader from 'components/CircularLoader';
|
||||
import { Empty } from 'antd';
|
||||
import HoverSocialCard from 'components/cards/statistics/HoverSocialCard';
|
||||
import { DashboardFilled, OpenAIFilled } from '@ant-design/icons';
|
||||
import { MdDirectionsBike } from 'react-icons/md';
|
||||
import { FaMapLocationDot } from 'react-icons/fa6';
|
||||
import { HiOutlineArrowLeft } from 'react-icons/hi';
|
||||
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
dayjs.extend(utc);
|
||||
|
||||
const OrdersPreview = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
console.log('location.state', location.state);
|
||||
const [rider, setRider] = useState(null);
|
||||
const [payment, setPayment] = useState(null);
|
||||
const [finaldeliveryList, setFinalDeliveryList] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const deliverylist = location.state?.deliverylist;
|
||||
const zoneData = location.state?.zoneData;
|
||||
const metaData = location.state?.metaData;
|
||||
const riderToken = location.state?.riderToken;
|
||||
const appId = location.state?.appId;
|
||||
const aiMode = location.state?.aiMode;
|
||||
const reassignOrders = location.state?.reassignOrders;
|
||||
|
||||
useEffect(() => {
|
||||
console.log('aiMode', aiMode);
|
||||
console.log('riderToken', riderToken);
|
||||
console.log('zoneData', zoneData);
|
||||
console.log('metaData', metaData);
|
||||
console.log('reassignOrders', reassignOrders);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!deliverylist?.length) return;
|
||||
const updateDeliveryAmtList = deliverylist.map((list) => {
|
||||
const cumulativeKms = Number(list.cumulativekms || 0);
|
||||
const minKm = Number(list.minkm || 0);
|
||||
const basePrice = Number(list.baseprice || 0);
|
||||
const pricePerKm = Number(list.priceperkm || 0);
|
||||
if (cumulativeKms <= minKm) {
|
||||
return {
|
||||
...list,
|
||||
deliveryamt: basePrice
|
||||
};
|
||||
}
|
||||
return {
|
||||
...list,
|
||||
deliveryamt: (cumulativeKms - minKm) * pricePerKm + basePrice
|
||||
};
|
||||
});
|
||||
|
||||
setFinalDeliveryList(updateDeliveryAmtList);
|
||||
console.log('finaldeliveryList', updateDeliveryAmtList);
|
||||
}, [deliverylist]);
|
||||
|
||||
// ==============================|| fetchPaymentType ||============================== //
|
||||
|
||||
const {
|
||||
data: paymentModes = [],
|
||||
isLoading: paymentModesLoading,
|
||||
isError: paymentModesError,
|
||||
error: paymentModesErrorMessage
|
||||
} = useQuery({
|
||||
queryKey: ['paymentmodes'],
|
||||
queryFn: fetchPaymentType
|
||||
});
|
||||
|
||||
// ==============================|| fetchRidersList ||============================== //
|
||||
|
||||
const {
|
||||
data: ridersList = [],
|
||||
isLoading: ridersListLoading,
|
||||
isError: ridersListError,
|
||||
error: ridersListErrorMessage,
|
||||
refetch: ridersListRefetch
|
||||
} = useQuery({
|
||||
queryKey: ['ridersList', appId], // Unique key for caching & re-fetching
|
||||
queryFn: fetchRidersList,
|
||||
enabled: appId !== 0 // Ensures query runs only when appId is valid
|
||||
});
|
||||
|
||||
const getRiderName = async (userid) => {
|
||||
await ridersList.map((rider) => {
|
||||
if (rider.userid == userid) {
|
||||
return rider.firstname;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ======================================================= || notifyRiderMutation || =======================================================
|
||||
|
||||
const notifyRiderMutation = useMutation({
|
||||
mutationFn: notifyRider, // Using the separate function
|
||||
onSuccess: () => {
|
||||
OpenToast('Notification sent Successfully', 'success', 2000);
|
||||
},
|
||||
onError: (error) => {
|
||||
OpenToast(error.message, 'error', 2000);
|
||||
}
|
||||
});
|
||||
|
||||
const createNormalDeliveryMutation = useMutation({
|
||||
mutationFn: finalCreatedeliveries, // for optimised delivery create
|
||||
|
||||
onSuccess: (data, variables) => {
|
||||
console.log('data', data);
|
||||
console.log('varialbles', variables);
|
||||
notifyRiderMutation.mutate(rider.userfcmtoken || riderToken); // Call notifyRider after success
|
||||
if (data.status == 'accepted') {
|
||||
OpenToast('Delivery Created Successfully', 'success', 2000);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
navigate('/nearle/deliveries');
|
||||
}, 2000);
|
||||
},
|
||||
onError: (error) => {
|
||||
OpenToast(error.message, 'error', 4000);
|
||||
}
|
||||
});
|
||||
|
||||
const handleManualCreateDelivery = async () => {
|
||||
setIsLoading(true);
|
||||
createNormalDeliveryMutation.mutate({
|
||||
deliveries: finaldeliveryList
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<MainCard
|
||||
content={false}
|
||||
title={
|
||||
<Stack direction="row" alignItems="center" spacing={1} sx={{ ml: 1 }}>
|
||||
<Tooltip title="Back to orders" placement="top">
|
||||
<IconButton
|
||||
onClick={() => navigate('/nearle/orders')}
|
||||
sx={{
|
||||
backgroundColor: 'action.hover',
|
||||
color: 'text.primary',
|
||||
'&:hover': {
|
||||
backgroundColor: 'action.selected'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<HiOutlineArrowLeft size={22} />
|
||||
</IconButton>{' '}
|
||||
</Tooltip>
|
||||
<Typography sx={{ m: 2 }} variant="h3">
|
||||
Assign Orders
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
secondary={
|
||||
<Button sx={{ m: 2 }} color="primary" variant="contained" startIcon={<ArrowBackIcon />}>
|
||||
Re-Assign
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{(paymentModesLoading || ridersListLoading || isLoading) && (
|
||||
<>
|
||||
<Loader />
|
||||
<CircularLoader />
|
||||
</>
|
||||
)}
|
||||
{
|
||||
<Backdrop
|
||||
sx={{
|
||||
color: '#fff',
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1
|
||||
}}
|
||||
open={paymentModesLoading || ridersListLoading || isLoading} // when loader = true, backdrop covers the page
|
||||
>
|
||||
<CircularLoader color="inherit" />
|
||||
</Backdrop>
|
||||
}
|
||||
|
||||
{aiMode == 1 && (
|
||||
<Stack sx={{ m: 2 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.total_orders}
|
||||
primary={'Orders'}
|
||||
percentage={<DashboardFilled />}
|
||||
color={theme.palette.success.main}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.total_riders}
|
||||
primary={'Riders'}
|
||||
percentage={<MdDirectionsBike />}
|
||||
color={theme.palette.warning.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<HoverSocialCard
|
||||
secondary={zoneData?.length}
|
||||
primary={'Zones'}
|
||||
percentage={<FaMapLocationDot />}
|
||||
color={theme.palette.info.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={3}>
|
||||
<HoverSocialCard
|
||||
secondary={zoneData?.length}
|
||||
primary={'Kilometer'}
|
||||
percentage={<FaMapLocationDot />}
|
||||
color={theme.palette.error.main}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
)}
|
||||
<TableContainer
|
||||
sx={{
|
||||
maxHeight: 'calc(100vh - 250px)',
|
||||
overflow: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '12px', // scroll bar width
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: theme.palette.primary.main, // thumb color
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
backgroundColor: theme.palette.primary.dark, // hover color
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
backgroundColor: theme.palette.primary.lighter,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Table stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow sx={{ backgroundColor: 'red' }}>
|
||||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>#</TableCell>
|
||||
{aiMode == 1 && (
|
||||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Zone </TableCell>
|
||||
)}
|
||||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Tenant </TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>order Location</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Pickup </TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Delivery</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Notes</TableCell>
|
||||
{aiMode == 1 && (
|
||||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Rider</TableCell>
|
||||
)}
|
||||
<TableCell align="center" sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>
|
||||
Type
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>
|
||||
Profit
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>
|
||||
Charges
|
||||
</TableCell>
|
||||
<TableCell align="center" sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>
|
||||
KMS
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{finaldeliveryList?.length == 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={10}>
|
||||
<Empty />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{finaldeliveryList && aiMode == 1 // ai mode , ai automation
|
||||
? finaldeliveryList?.map((val, index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<TableRow sx={{}}>
|
||||
<TableCell>
|
||||
<Typography> {index + 1}</Typography>
|
||||
</TableCell>
|
||||
{aiMode == 1 && (
|
||||
<TableCell>
|
||||
<Chip color="primary" label={val.zone_name} />
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<Tooltip title={val.tenantaddress}>
|
||||
<Typography variant="body1" noWrap>
|
||||
{val.tenantname}
|
||||
</Typography>
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{val.tenantsuburb}
|
||||
<br />
|
||||
</Typography>
|
||||
|
||||
<Typography noWrap variant="body2">
|
||||
{val.applocation}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Tooltip title={val.locationaddress} placement="top">
|
||||
<Typography variant="body1" noWrap>
|
||||
{`${val.locationname}-(${val.locationsuburb})`}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title="Order Id">
|
||||
<Typography variant="body2" noWrap>
|
||||
{val.orderid}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Stack display={'flex'} flexDirection={'row'} gap={3}>
|
||||
<Tooltip title="Ordered date">
|
||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||
{dayjs(val.orderdate).utc().format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{dayjs(val.orderdate).utc().format('hh:mm A')}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
-
|
||||
<Tooltip title="Delivery date">
|
||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||
{dayjs(val.deliverydate).utc().format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{dayjs(val.deliverydate).utc().format('hh:mm A')}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack direction={'row'} spacing={1}>
|
||||
<Stack direction="column">
|
||||
<Typography variant="caption">{val.pickupcustomer}</Typography>
|
||||
<Typography variant="caption">{val.pickupcontactno}</Typography>
|
||||
<Tooltip title={val.pickupaddress}>
|
||||
<Typography variant="caption">{val.pickupsuburb || val.pickupaddress.slice(0, 20)}</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack direction={'row'} spacing={1}>
|
||||
<Stack direction="column">
|
||||
<Typography variant="caption">{val.deliverycustomer}</Typography>
|
||||
<Typography variant="caption">{val.deliverycontactno}</Typography>
|
||||
<Tooltip title={val.deliveryaddress}>
|
||||
<Typography variant="caption">{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">{val.ordernotes}</TableCell>
|
||||
{aiMode == 1 && (
|
||||
<TableCell align="left">
|
||||
<Typography sx={{ whiteSpace: 'nowrap' }}>{val.username}</Typography>
|
||||
<Typography>ID : {val.userid}</Typography>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell align="center">
|
||||
<Chip
|
||||
size="small"
|
||||
label={val.ordertype}
|
||||
color={val.ordertype == 'Economy' ? 'success' : val.ordertype == 'Risky' ? 'error' : 'primary'}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
|
||||
<Tooltip title="Charges" placement="top">
|
||||
<Chip size="small" label={`₹ ${val.deliverycharge.toFixed(2)} `} color="error" />
|
||||
</Tooltip>
|
||||
<Tooltip title="Amount" placement="left">
|
||||
<Chip size="small" label={`₹ ${val.deliveryamt.toFixed(2)} `} color="success" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
|
||||
<Tooltip title="KMS" placement="top">
|
||||
<Chip size="small" label={`${val.kms} km`} color="error" />
|
||||
</Tooltip>
|
||||
<Tooltip title="Cumulative Kms" placement="right">
|
||||
<Chip size="small" label={`${val.cumulativekms} km`} color="success" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Fragment>
|
||||
);
|
||||
})
|
||||
: // normal optimisation
|
||||
finaldeliveryList?.map((val, index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<TableRow sx={{}}>
|
||||
<TableCell>
|
||||
<Typography> {index + 1}</Typography>
|
||||
</TableCell>
|
||||
{/* {aiMode == 1 && (
|
||||
<TableCell>
|
||||
<Chip color="primary" label={val.zone_name} />
|
||||
</TableCell>
|
||||
)} */}
|
||||
<TableCell>
|
||||
<Tooltip title={val.tenantaddress}>
|
||||
<Typography variant="body1" noWrap>
|
||||
{val.tenantname}
|
||||
</Typography>
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{val.tenantsuburb}
|
||||
<br />
|
||||
</Typography>
|
||||
|
||||
<Typography noWrap variant="body2">
|
||||
{val.applocation}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Tooltip title={val.locationaddress} placement="top">
|
||||
<Typography variant="body1" noWrap>
|
||||
{`${val.locationname}-(${val.locationsuburb})`}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title="Order Id">
|
||||
<Typography variant="body2" noWrap>
|
||||
{val.orderid}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Stack display={'flex'} flexDirection={'row'} gap={3}>
|
||||
<Tooltip title="Ordered date">
|
||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||
{dayjs(val.orderdate).utc().format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{dayjs(val.orderdate).utc().format('hh:mm A')}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
-
|
||||
<Tooltip title="Delivery date">
|
||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||
{dayjs(val.deliverydate).utc().format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{dayjs(val.deliverydate).utc().format('hh:mm A')}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack direction={'row'} spacing={1}>
|
||||
<Stack direction="column">
|
||||
<Typography variant="caption">{val.pickupcustomer}</Typography>
|
||||
<Typography variant="caption">{val.pickupcontactno}</Typography>
|
||||
<Tooltip title={val.pickupaddress}>
|
||||
<Typography variant="caption">{val.pickupsuburb || val.pickupaddress.slice(0, 20)}</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack direction={'row'} spacing={1}>
|
||||
<Stack direction="column">
|
||||
<Typography variant="caption">{val.deliverycustomer}</Typography>
|
||||
<Typography variant="caption">{val.deliverycontactno}</Typography>
|
||||
<Tooltip title={val.deliveryaddress}>
|
||||
<Typography variant="caption">{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">{val.ordernotes}</TableCell>
|
||||
{/* {aiMode == 1 && (
|
||||
<TableCell align="left">
|
||||
<Typography sx={{ whiteSpace: 'nowrap' }}>{val.username}</Typography>
|
||||
<Typography>ID : {val.userid}</Typography>
|
||||
</TableCell>
|
||||
)} */}
|
||||
<TableCell align="center">
|
||||
<Chip
|
||||
size="small"
|
||||
label={val.ordertype}
|
||||
color={val.ordertype == 'Economy' ? 'success' : val.ordertype == 'Risky' ? 'error' : 'primary'}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
|
||||
<Tooltip title="Charges" placement="top">
|
||||
<Chip size="small" label={`₹ ${val.deliverycharge.toFixed(2)} `} color="error" />
|
||||
</Tooltip>
|
||||
<Tooltip title="Amount" placement="left">
|
||||
<Chip size="small" label={`₹ ${val.deliveryamt.toFixed(2)} `} color="success" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
|
||||
<Tooltip title="KMS" placement="top">
|
||||
<Chip size="small" label={`${val.kms} km`} color="error" />
|
||||
</Tooltip>
|
||||
<Tooltip title="Cumulative Kms" placement="right">
|
||||
<Chip size="small" label={`${val.cumulativekms} km`} color="success" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Divider />
|
||||
{aiMode == 0 && (
|
||||
<Grid container spacing={2} sx={{ p: 2 }}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Autocomplete
|
||||
id="free-solo-demo"
|
||||
options={paymentModes}
|
||||
renderInput={(params) => <TextField {...params} label="Choose Payment" />}
|
||||
onChange={(event, newValue, reason) => {
|
||||
if (reason === 'clear') {
|
||||
setPayment(null);
|
||||
return;
|
||||
}
|
||||
if (newValue) {
|
||||
console.log('Selected:', newValue);
|
||||
setPayment(newValue);
|
||||
const newList = finaldeliveryList?.map((list) => ({
|
||||
...list,
|
||||
paymenttype: newValue.apptypeid // merge selected rider into each list item
|
||||
}));
|
||||
setFinalDeliveryList(newList);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Autocomplete
|
||||
id="free-solo-demo"
|
||||
options={ridersList}
|
||||
renderInput={(params) => <TextField {...params} label="Choose Rider" />}
|
||||
onChange={(event, newValue, reason) => {
|
||||
if (reason === 'clear') {
|
||||
setRider(null);
|
||||
return;
|
||||
}
|
||||
if (newValue) {
|
||||
setRider(newValue);
|
||||
console.log('Selected:', newValue);
|
||||
const newList = finaldeliveryList?.map((list) => ({
|
||||
...list,
|
||||
userid: newValue.userid,
|
||||
userfcmtoken: newValue.userfcmtoken
|
||||
}));
|
||||
setFinalDeliveryList(newList);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
<Divider />
|
||||
<Stack display={'flex'} flexDirection={'row'} gap={2} alignItems={'center'} justifyContent={'end'} sx={{ p: 2 }}>
|
||||
<Button
|
||||
sx={{}}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<ArrowBackIcon />}
|
||||
onClick={() => {
|
||||
navigate('/nearle/orders');
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button sx={{ my: 2 }} variant="contained" disabled={aiMode === 0 && (!rider || !payment)} onClick={handleManualCreateDelivery}>
|
||||
Assign Orders
|
||||
</Button>
|
||||
</Stack>
|
||||
</MainCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrdersPreview;
|
||||
49
src/pages/nearle/orders/OrdersTableSkeleton.js
Normal file
49
src/pages/nearle/orders/OrdersTableSkeleton.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { TableRow, TableCell, Skeleton, Stack } from '@mui/material';
|
||||
|
||||
export const OrdersTableSkeleton = ({ rowsPerPage = 5, col = 1 }) => {
|
||||
return (
|
||||
<>
|
||||
{Array.from(new Array(rowsPerPage)).map((_, index) => (
|
||||
<TableRow key={index}>
|
||||
{/* Checkbox */}
|
||||
<TableCell>
|
||||
<Skeleton variant="circular" width={24} height={24} />
|
||||
</TableCell>
|
||||
|
||||
{/* Serial Number */}
|
||||
<TableCell>
|
||||
<Skeleton variant="text" width={30} />
|
||||
</TableCell>
|
||||
|
||||
{/* Delivery Info */}
|
||||
{Array.from({ length: col }).map((_, index) => (
|
||||
<TableCell key={index}>
|
||||
<Stack spacing={0.5}>
|
||||
<Skeleton variant="text" width={100} />
|
||||
<Skeleton variant="text" width={80} />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
))}
|
||||
|
||||
{/* Notes */}
|
||||
<TableCell>
|
||||
<Skeleton variant="text" width={150} />
|
||||
</TableCell>
|
||||
|
||||
{/* Order Status */}
|
||||
<TableCell>
|
||||
<Skeleton variant="rounded" width={60} height={24} />
|
||||
</TableCell>
|
||||
|
||||
{/* Actions */}
|
||||
<TableCell align="center">
|
||||
<Stack direction="row" spacing={1} justifyContent="flex-end">
|
||||
<Skeleton variant="circular" width={28} height={28} />
|
||||
<Skeleton variant="circular" width={28} height={28} />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
2108
src/pages/nearle/orders/createorder1.js
Normal file
2108
src/pages/nearle/orders/createorder1.js
Normal file
File diff suppressed because it is too large
Load Diff
1974
src/pages/nearle/orders/details.js
Normal file
1974
src/pages/nearle/orders/details.js
Normal file
File diff suppressed because it is too large
Load Diff
1625
src/pages/nearle/orders/multipleOrders.js
Normal file
1625
src/pages/nearle/orders/multipleOrders.js
Normal file
File diff suppressed because it is too large
Load Diff
1322
src/pages/nearle/orders/multipleorders_copy.js
Normal file
1322
src/pages/nearle/orders/multipleorders_copy.js
Normal file
File diff suppressed because it is too large
Load Diff
2190
src/pages/nearle/orders/newcreateOrder.js
Normal file
2190
src/pages/nearle/orders/newcreateOrder.js
Normal file
File diff suppressed because it is too large
Load Diff
610
src/pages/nearle/orders/optimisedOrderPreview.js
Normal file
610
src/pages/nearle/orders/optimisedOrderPreview.js
Normal file
@@ -0,0 +1,610 @@
|
||||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
Chip,
|
||||
Divider,
|
||||
Grid,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Backdrop,
|
||||
IconButton,
|
||||
Badge,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionActions,
|
||||
AccordionDetails
|
||||
} from '@mui/material';
|
||||
import React, { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import dayjs from 'dayjs';
|
||||
import MainCard from 'components/MainCard';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { createAutomationDeliveries, fetchPaymentType, fetchRidersList, finalCreatedeliveries, notifyRider } from 'pages/api/api';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import Loader from 'components/Loader';
|
||||
import CircularLoader from 'components/CircularLoader';
|
||||
import { Empty } from 'antd';
|
||||
import HoverSocialCard from 'components/cards/statistics/HoverSocialCard';
|
||||
import { DashboardFilled, DashboardOutlined, OpenAIFilled } from '@ant-design/icons';
|
||||
import { MdDirectionsBike } from 'react-icons/md';
|
||||
import { FaMapLocationDot } from 'react-icons/fa6';
|
||||
import { HiOutlineArrowLeft } from 'react-icons/hi';
|
||||
import { IoReload } from 'react-icons/io5';
|
||||
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
||||
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
||||
import { GiProfit } from 'react-icons/gi';
|
||||
import { ArrowRightAltOutlined } from '@mui/icons-material';
|
||||
import LocalShippingIcon from '@mui/icons-material/LocalShipping';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import BoltIcon from '@mui/icons-material/Bolt';
|
||||
import CurrencyRupeeIcon from '@mui/icons-material/CurrencyRupee';
|
||||
import SavingsIcon from '@mui/icons-material/Savings';
|
||||
import EnergySavingsLeafIcon from '@mui/icons-material/EnergySavingsLeaf';
|
||||
import DirectionsBikeOutlinedIcon from '@mui/icons-material/DirectionsBikeOutlined';
|
||||
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
|
||||
import { exportToExcel } from 'components/exportToExcel';
|
||||
import CSVExport from 'components/third-party/ReactTable';
|
||||
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
dayjs.extend(utc);
|
||||
|
||||
const OptimisedOrderPreview = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
console.log('location.state', location.state);
|
||||
const [rider, setRider] = useState(null);
|
||||
const [payment, setPayment] = useState(null);
|
||||
const [finaldeliveryList, setFinalDeliveryList] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [deliverylist, setDeliverylist] = useState(location.state?.deliverylist);
|
||||
const [zoneData, setZoneData] = useState(location?.state?.zoneData);
|
||||
const [metaData, setMetaData] = useState(location.state?.metaData);
|
||||
const riderToken = location.state?.riderToken;
|
||||
const appId = location.state?.appId;
|
||||
const aiMode = location.state?.aiMode;
|
||||
const reassignOrders = location.state?.reassignOrders;
|
||||
const [csvExportData, setCsvExportData] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('called');
|
||||
|
||||
console.log('aiMode', aiMode);
|
||||
console.log('riderToken', riderToken);
|
||||
console.log('zoneData', zoneData);
|
||||
console.log('metaData', metaData);
|
||||
console.log('reassignOrders', reassignOrders);
|
||||
}, [zoneData, metaData, deliverylist]);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = finaldeliveryList.map((item) => ({
|
||||
zone_name: item.zone_name,
|
||||
ordernotes: item.ordernotes,
|
||||
rider: item.rider,
|
||||
step: item.step,
|
||||
ordertype: item.ordertype,
|
||||
orderamount: item.orderamount,
|
||||
riderkms: item.riderkms,
|
||||
cumulativekms: item.cumulativekms,
|
||||
baseprice: item.baseprice,
|
||||
minkm: item.minkm,
|
||||
priceperkm: item.priceperkm,
|
||||
kms: item.kms,
|
||||
actualkms: item.actualkms,
|
||||
rider_charge: item.rider_charge,
|
||||
deliveryamt: item.deliveryamt,
|
||||
deliverycharges: item.deliverycharges,
|
||||
profit: item.profit
|
||||
}));
|
||||
|
||||
setCsvExportData(filtered);
|
||||
|
||||
console.log('csvExportData', filtered);
|
||||
}, [finaldeliveryList]);
|
||||
useEffect(() => {
|
||||
if (!deliverylist?.length) return;
|
||||
const updateDeliveryAmtList = deliverylist.map((list) => {
|
||||
const cumulativeKms = Number(list.cumulativekms || 0);
|
||||
const minKm = Number(list.minkm || 0);
|
||||
const basePrice = Number(list.baseprice || 0);
|
||||
const pricePerKm = Number(list.priceperkm || 0);
|
||||
if (cumulativeKms <= minKm) {
|
||||
return {
|
||||
...list,
|
||||
deliveryamt: basePrice
|
||||
};
|
||||
}
|
||||
return {
|
||||
...list,
|
||||
deliveryamt: (cumulativeKms - minKm) * pricePerKm + basePrice
|
||||
};
|
||||
});
|
||||
|
||||
setFinalDeliveryList(updateDeliveryAmtList);
|
||||
console.log('finaldeliveryList', updateDeliveryAmtList);
|
||||
}, [deliverylist]);
|
||||
|
||||
// ==============================|| fetchPaymentType ||============================== //
|
||||
|
||||
const {
|
||||
data: paymentModes = [],
|
||||
isLoading: paymentModesLoading,
|
||||
isError: paymentModesError,
|
||||
error: paymentModesErrorMessage
|
||||
} = useQuery({
|
||||
queryKey: ['paymentmodes'],
|
||||
queryFn: fetchPaymentType
|
||||
});
|
||||
|
||||
// ==============================|| fetchRidersList ||============================== //
|
||||
|
||||
const {
|
||||
data: ridersList = [],
|
||||
isLoading: ridersListLoading,
|
||||
isError: ridersListError,
|
||||
error: ridersListErrorMessage,
|
||||
refetch: ridersListRefetch
|
||||
} = useQuery({
|
||||
queryKey: ['ridersList', appId], // Unique key for caching & re-fetching
|
||||
queryFn: fetchRidersList,
|
||||
enabled: appId !== 0 // Ensures query runs only when appId is valid
|
||||
});
|
||||
|
||||
const getRiderName = async (userid) => {
|
||||
await ridersList.map((rider) => {
|
||||
if (rider.userid == userid) {
|
||||
return rider.firstname;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ======================================================= || notifyRiderMutation || =======================================================
|
||||
|
||||
const notifyRiderMutation = useMutation({
|
||||
mutationFn: notifyRider, // Using the separate function
|
||||
onSuccess: () => {
|
||||
OpenToast('Notification sent Successfully', 'success', 2000);
|
||||
},
|
||||
onError: (error) => {
|
||||
OpenToast(error.message, 'error', 2000);
|
||||
}
|
||||
});
|
||||
|
||||
const createFinalDeliveryMutation = useMutation({
|
||||
mutationFn: finalCreatedeliveries, // for optimised delivery create
|
||||
|
||||
onSuccess: (data, variables) => {
|
||||
console.log('data', data);
|
||||
console.log('varialbles', variables);
|
||||
notifyRiderMutation.mutate(rider.userfcmtoken || riderToken); // Call notifyRider after success
|
||||
if (data.status == 'accepted') {
|
||||
OpenToast('Delivery Created Successfully', 'success', 2000);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
OpenToast(error.message, 'error', 4000);
|
||||
},
|
||||
onSettled: () => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
navigate('/nearle/deliveries');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
const handleFinalCreateDelivery = async () => {
|
||||
// console.log('Final delivery list:', finaldeliveryList);
|
||||
|
||||
setIsLoading(true);
|
||||
createFinalDeliveryMutation.mutate({
|
||||
deliveries: finaldeliveryList
|
||||
});
|
||||
};
|
||||
const createDeliveryMutation = useMutation({
|
||||
// 0 -> manual rider assign 1 -> ai rider assign
|
||||
mutationFn: createAutomationDeliveries,
|
||||
onSuccess: (data, variables) => {
|
||||
console.log('data', data);
|
||||
console.log('varialbles', variables);
|
||||
OpenToast('Orders Optimised Successfully', 'success', 2000);
|
||||
// notifyRiderMutation.mutate(variables?.riderToken); // Call notifyRider after success
|
||||
setZoneData(data?.zones);
|
||||
setDeliverylist(data?.details);
|
||||
setMetaData(data?.meta);
|
||||
setIsLoading(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
OpenToast(error.message, 'error', 4000);
|
||||
setIsLoading(false);
|
||||
},
|
||||
onSettled: () => {
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
const tuningTypes = [
|
||||
{
|
||||
tuneid: 1,
|
||||
type: 'Rider'
|
||||
},
|
||||
{
|
||||
tuneid: 2,
|
||||
type: 'Pickup'
|
||||
},
|
||||
{
|
||||
tuneid: 3,
|
||||
type: 'Zone'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{
|
||||
<Backdrop
|
||||
sx={{
|
||||
color: '#fff',
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1
|
||||
}}
|
||||
open={paymentModesLoading || ridersListLoading || isLoading} // when loader = true, backdrop covers the page
|
||||
>
|
||||
<CircularLoader color="inherit" />
|
||||
</Backdrop>
|
||||
}
|
||||
{(paymentModesLoading || ridersListLoading || isLoading) && (
|
||||
<>
|
||||
<Loader />
|
||||
<CircularLoader />
|
||||
</>
|
||||
)}
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{}}>
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<Tooltip title="Back to orders" placement="top">
|
||||
<IconButton
|
||||
onClick={() => navigate('/nearle/orders')}
|
||||
sx={{
|
||||
bgcolor: 'action.hover',
|
||||
'&:hover': { bgcolor: 'action.selected' }
|
||||
}}
|
||||
>
|
||||
<HiOutlineArrowLeft size={20} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Typography variant="h3" fontWeight={600}>
|
||||
Assign Orders
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{/* <Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<IoReload />}
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
createDeliveryMutation.mutate({ deliveries: reassignOrders });
|
||||
}}
|
||||
>
|
||||
Tuning
|
||||
</Button> */}
|
||||
<Autocomplete
|
||||
multiple
|
||||
id="checkboxes-tags-demo"
|
||||
options={tuningTypes}
|
||||
disableCloseOnSelect
|
||||
getOptionLabel={(option) => option.type}
|
||||
renderOption={(props, option, { selected }) => {
|
||||
const { id, ...optionProps } = props;
|
||||
const SelectionIcon = selected ? CheckBoxIcon : CheckBoxOutlineBlankIcon;
|
||||
|
||||
return (
|
||||
<li key={id} {...optionProps}>
|
||||
<SelectionIcon fontSize="small" style={{ marginRight: 8, padding: 9, boxSizing: 'content-box' }} />
|
||||
{option.type}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
style={{ width: 400 }}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
sx={{
|
||||
'& .MuiInputBase-input': {
|
||||
lineHeight: '1.5' // 👈 important
|
||||
}
|
||||
}}
|
||||
label="Hyper Tuning"
|
||||
// placeholder="Favorites"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{/* <Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<DownloadOutlinedIcon />}
|
||||
onClick={() => {
|
||||
exportToExcel(finaldeliveryList, 'users', 'UsersData');
|
||||
}}
|
||||
>
|
||||
Export Report
|
||||
</Button> */}
|
||||
<CSVExport
|
||||
data={csvExportData}
|
||||
filename={`Orders_Detail_${dayjs().format('YYYY-MM-DD_HHmmss')}.csv`}
|
||||
label=" CSV"
|
||||
style={{ m: 1 }}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack sx={{ my: 2 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6} sm={6} md={3}>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.total_orders}
|
||||
primary={'Orders'}
|
||||
percentage={<DashboardFilled />}
|
||||
color={theme.palette.success.main}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={3}>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.utilized_riders}
|
||||
primary={'Riders'}
|
||||
percentage={<MdDirectionsBike />}
|
||||
color={theme.palette.warning.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={3}>
|
||||
<HoverSocialCard
|
||||
secondary={zoneData?.length}
|
||||
primary={'Zones'}
|
||||
percentage={<FaMapLocationDot />}
|
||||
color={theme.palette.info.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={3}>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.total_profit}
|
||||
primary={'Profit'}
|
||||
percentage={<GiProfit />}
|
||||
color={theme.palette.error.main}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
<MainCard content={false}>
|
||||
{zoneData?.map((zone, index) => {
|
||||
const riders = zone?.riders.flatMap((rider) => rider);
|
||||
const orders = zone?.riders?.flatMap((rider) => rider.orders);
|
||||
|
||||
return (
|
||||
<Accordion key={index}>
|
||||
<AccordionSummary>
|
||||
<Stack direction="row" alignItems="center" spacing={2} sx={{ m: 2 }}>
|
||||
<Typography variant="h5" fontWeight={1000}>
|
||||
Zone {index + 1} :
|
||||
</Typography>
|
||||
<Chip label={zone.zone_name} color="primary" variant="light" size="small" sx={{ minWidth: 100 }} />
|
||||
<Tooltip title={`Orders`} placement="top">
|
||||
<Badge badgeContent={zone.total_orders} color="secondary">
|
||||
<DashboardOutlined style={{ fontSize: 20 }} />
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
<Tooltip title={`Riders`} placement="top">
|
||||
<Badge color="secondary" badgeContent={zone.active_riders_count}>
|
||||
<DirectionsBikeOutlinedIcon style={{ fontSize: 20 }} />
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<TableContainer sx={{ p: 0, m: 0 }}>
|
||||
<Table stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>#</TableCell>
|
||||
{/* <TableCell>Tenant</TableCell> */}
|
||||
<TableCell>Order Location</TableCell>
|
||||
<TableCell>Pickup</TableCell>
|
||||
<TableCell>Delivery</TableCell>
|
||||
<TableCell>Notes</TableCell>
|
||||
<TableCell>Rider</TableCell>
|
||||
<TableCell align="center">Type</TableCell>
|
||||
<TableCell align="center">Profit</TableCell>
|
||||
<TableCell align="center">Charges</TableCell>
|
||||
<TableCell align="center">KMS</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{orders.map((val, i) => (
|
||||
<Fragment key={i}>
|
||||
<TableRow sx={{}}>
|
||||
<TableCell>
|
||||
<Typography> {i + 1}</Typography>
|
||||
</TableCell>
|
||||
{/* <TableCell>
|
||||
<Tooltip title={val.tenantaddress}>
|
||||
<Typography variant="body1" noWrap>
|
||||
{val.tenantname}
|
||||
</Typography>
|
||||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||||
{val.tenantsuburb}
|
||||
<br />
|
||||
</Typography>
|
||||
|
||||
<Typography noWrap variant="body2">
|
||||
{val.applocation}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</TableCell> */}
|
||||
<TableCell align="left">
|
||||
<Tooltip title={val.locationaddress} placement="top">
|
||||
<Typography variant="body1" noWrap>
|
||||
{`${val.locationname}-(${val.locationsuburb})`}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title="Order Id">
|
||||
<Typography variant="body2" noWrap>
|
||||
{val.orderid}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Stack display={'flex'} flexDirection={'row'} gap={3} sx={{ cursor: 'pointer' }}>
|
||||
<Tooltip title="Pickup Time">
|
||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||
{dayjs(val.pickupslot).format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
<Chip
|
||||
size="small"
|
||||
label={dayjs(val.pickupslot).format('hh:mm A')}
|
||||
variant="light"
|
||||
sx={{ color: '#0a803b', bgcolor: '#c3f3c7' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
<ArrowRightAltOutlined />
|
||||
<Tooltip title="Estimated Delivery time">
|
||||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||||
{dayjs(val.expecteddeliverytime).format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
<Chip
|
||||
size="small"
|
||||
label={dayjs(val.expecteddeliverytime).format('hh:mm A')}
|
||||
variant="light"
|
||||
sx={{ color: '#DD2C00', background: '#FBE9E7' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack direction={'row'} spacing={1}>
|
||||
<Stack direction="column">
|
||||
<Typography variant="caption">{val.pickupcustomer}</Typography>
|
||||
<Typography variant="caption">{val.pickupcontactno}</Typography>
|
||||
<Tooltip title={val.pickupaddress}>
|
||||
<Typography sx={{ cursor: 'pointer' }} variant="caption">
|
||||
{val.pickupsuburb || val.pickupaddress.slice(0, 20)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack direction={'row'} spacing={1}>
|
||||
<Stack direction="column">
|
||||
<Typography variant="caption">{val.deliverycustomer}</Typography>
|
||||
<Typography variant="caption">{val.deliverycontactno}</Typography>
|
||||
<Tooltip title={val.deliveryaddress}>
|
||||
<Typography sx={{ cursor: 'pointer' }} variant="caption">
|
||||
{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Typography variant="caption" sx={{ whiteSpace: 'nowrap' }}>
|
||||
{val.ordernotes}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="left">
|
||||
<Typography variant="caption" sx={{ whiteSpace: 'nowrap' }}>
|
||||
{val.rider}
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography variant="caption">ID : {val.userid}</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Chip
|
||||
size="small"
|
||||
label={val.ordertype}
|
||||
icon={
|
||||
val.ordertype === 'Economy' ? (
|
||||
<EnergySavingsLeafIcon />
|
||||
) : val.ordertype === 'Risky' ? (
|
||||
<WarningIcon />
|
||||
) : (
|
||||
<BoltIcon />
|
||||
)
|
||||
}
|
||||
color={val.ordertype === 'Economy' ? 'success' : val.ordertype === 'Risky' ? 'error' : 'primary'}
|
||||
variant="light"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Chip
|
||||
size="small"
|
||||
label={`₹ ${parseFloat(val?.profit).toFixed(2)}`}
|
||||
variant="light"
|
||||
sx={{ color: '#009688', bgcolor: '#b2dfdb' }}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
|
||||
<Tooltip title="Charges" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={`₹ ${val.deliverycharge.toFixed(2)} `}
|
||||
variant="light"
|
||||
sx={{ color: '#0074e7', bgcolor: '#cce3fa' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
{/* <Tooltip title="Amount" placement="left">
|
||||
<Chip size="small" label={`₹ ${val.deliveryamt.toFixed(2)} `} color="success" variant="light" />
|
||||
</Tooltip> */}
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
|
||||
<Tooltip title="KMS" placement="top">
|
||||
<Chip size="small" label={`${val.kms} km`} color="error" variant="light" />
|
||||
</Tooltip>
|
||||
<Tooltip title="Cumulative Kms" placement="right">
|
||||
<Chip size="small" label={`${val.cumulativekms} km`} color="success" variant="light" />
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Fragment>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
})}
|
||||
|
||||
<Divider />
|
||||
<Stack display={'flex'} flexDirection={'row'} gap={2} alignItems={'center'} justifyContent={'end'} sx={{ p: 2 }}>
|
||||
<Button
|
||||
sx={{}}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<ArrowBackIcon />}
|
||||
onClick={() => {
|
||||
navigate('/nearle/orders');
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button sx={{ my: 2 }} variant="contained" disabled={aiMode === 0 && (!rider || !payment)} onClick={handleFinalCreateDelivery}>
|
||||
Assign Orders
|
||||
</Button>
|
||||
</Stack>
|
||||
</MainCard>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default OptimisedOrderPreview;
|
||||
2551
src/pages/nearle/orders/orders.js
Normal file
2551
src/pages/nearle/orders/orders.js
Normal file
File diff suppressed because it is too large
Load Diff
91
src/pages/nearle/reports/MapWithRouteGoogle.js
Normal file
91
src/pages/nearle/reports/MapWithRouteGoogle.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { LoadScriptNext, GoogleMap } from '@react-google-maps/api';
|
||||
|
||||
const containerStyle = {
|
||||
width: '100%',
|
||||
height: '90vh'
|
||||
};
|
||||
|
||||
const MapWithRouteGoogle = ({ coordinates, additionalProps, setMapOpen }) => {
|
||||
const mapRef = useRef(null);
|
||||
|
||||
/** Convert coordinates to numbers */
|
||||
const numericCoordinates = coordinates
|
||||
.map((c) => {
|
||||
const lat = Number(c.lat);
|
||||
const lng = Number(c.lng);
|
||||
return isNaN(lat) || isNaN(lng) ? null : { lat, lng };
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (numericCoordinates.length < 2) {
|
||||
return <div>No route data available</div>;
|
||||
}
|
||||
|
||||
const start = numericCoordinates[0];
|
||||
const end = numericCoordinates[numericCoordinates.length - 1];
|
||||
|
||||
/** Map loaded callback */
|
||||
const onMapLoad = (map) => {
|
||||
// draw markers
|
||||
new window.google.maps.Marker({
|
||||
position: start,
|
||||
map,
|
||||
label: 'S',
|
||||
title: `Start: ${additionalProps?.riderStart}`
|
||||
});
|
||||
|
||||
new window.google.maps.Marker({
|
||||
position: end,
|
||||
map,
|
||||
label: 'E',
|
||||
title: `End: ${additionalProps?.riderEnd}`
|
||||
});
|
||||
|
||||
// draw rider route (point-to-point)
|
||||
const route = new window.google.maps.Polyline({
|
||||
path: numericCoordinates,
|
||||
geodesic: false,
|
||||
strokeColor: '#1A73E8',
|
||||
strokeOpacity: 1.0,
|
||||
strokeWeight: 4
|
||||
});
|
||||
|
||||
route.setMap(map);
|
||||
|
||||
// auto fit
|
||||
const bounds = new window.google.maps.LatLngBounds();
|
||||
numericCoordinates.forEach((p) => bounds.extend(p));
|
||||
map.fitBounds(bounds);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setMapOpen(false)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
zIndex: 999,
|
||||
padding: '6px 12px',
|
||||
background: '#1A73E8',
|
||||
color: 'white',
|
||||
borderRadius: 6,
|
||||
cursor: 'pointer',
|
||||
border: 'none'
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
||||
<LoadScriptNext googleMapsApiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
|
||||
<GoogleMap mapContainerStyle={containerStyle} center={start} zoom={14} onLoad={onMapLoad}>
|
||||
{/* Polyline and markers added via onLoad */}
|
||||
</GoogleMap>
|
||||
</LoadScriptNext>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapWithRouteGoogle;
|
||||
76
src/pages/nearle/reports/RiderLocationMap.js
Normal file
76
src/pages/nearle/reports/RiderLocationMap.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Button } from '@mui/material';
|
||||
import { LoadScriptNext, GoogleMap, Marker, OverlayView } from '@react-google-maps/api';
|
||||
|
||||
const containerStyle = {
|
||||
width: '100%',
|
||||
height: 'calc(100vh - 150px)'
|
||||
};
|
||||
|
||||
export default function RiderLocationMap({ riderLocations }) {
|
||||
console.log('riderLocations', riderLocations);
|
||||
|
||||
const center = {
|
||||
lat: Number(riderLocations?.[0]?.latitude || 11.0056),
|
||||
lng: Number(riderLocations?.[0]?.longitude || 76.9661)
|
||||
};
|
||||
const GreenIcon = {
|
||||
url: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
|
||||
scaledSize: new window.google.maps.Size(25, 41),
|
||||
anchor: new window.google.maps.Point(12, 41)
|
||||
};
|
||||
|
||||
const RedIcon = {
|
||||
url: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||
scaledSize: new window.google.maps.Size(25, 41),
|
||||
anchor: new window.google.maps.Point(12, 41)
|
||||
};
|
||||
|
||||
return (
|
||||
<LoadScriptNext googleMapsApiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
|
||||
<GoogleMap mapContainerStyle={containerStyle} zoom={12} center={center}>
|
||||
{riderLocations &&
|
||||
riderLocations?.map((r, index) => {
|
||||
const lat = Number(r.latitude);
|
||||
const lng = Number(r.longitude);
|
||||
return (
|
||||
<div key={index}>
|
||||
{/* Marker */}
|
||||
<Marker
|
||||
position={{ lat, lng }}
|
||||
icon={r.status == 'active' ? GreenIcon : RedIcon}
|
||||
label={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
/>
|
||||
<OverlayView position={{ lat, lng }} mapPaneName={OverlayView.OVERLAY_LAYER}>
|
||||
<div
|
||||
style={{
|
||||
background: 'none',
|
||||
color: 'green',
|
||||
padding: '2px 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
whiteSpace: 'nowrap',
|
||||
transform: 'translate(-50%, -140%)',
|
||||
pointerEvents: 'none',
|
||||
ml: 20
|
||||
}}
|
||||
>
|
||||
<Button variant="contained" color="primary" size="small">
|
||||
{` ${r.username} `}
|
||||
{/* <br /> */}
|
||||
{/* {`${r.contactno || '##### ##### '} `} */}
|
||||
<br />
|
||||
{`(${r.orderid || ''}) `}
|
||||
</Button>
|
||||
</div>
|
||||
</OverlayView>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</GoogleMap>
|
||||
</LoadScriptNext>
|
||||
);
|
||||
}
|
||||
69
src/pages/nearle/reports/RidersRoutes.js
Normal file
69
src/pages/nearle/reports/RidersRoutes.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { GoogleMap, Polyline, Marker, useJsApiLoader } from '@react-google-maps/api';
|
||||
|
||||
const containerStyle = {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
};
|
||||
|
||||
export default function RidersRoutes({ details }) {
|
||||
const mapRef = useRef(null);
|
||||
|
||||
const { isLoaded } = useJsApiLoader({
|
||||
googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_KEY
|
||||
});
|
||||
|
||||
// Convert dataset
|
||||
const routePath = useMemo(
|
||||
() =>
|
||||
details?.map((p) => ({
|
||||
lat: Number(p.latitude),
|
||||
lng: Number(p.longitude)
|
||||
})),
|
||||
[details]
|
||||
);
|
||||
const bikeIcon = {
|
||||
path: 'M12 2c-2.2 0-4 1.8-4 4v3H5l-1 2h2l3.6 7.59c.34.58.96.94 1.64.94h2.52c.68 0 1.3-.36 1.64-.94L19 11h2l-1-2h-3V6c0-2.2-1.8-4-4-4z',
|
||||
fillColor: '#9c27b0', // 🔥 purple
|
||||
fillOpacity: 1,
|
||||
strokeWeight: 0,
|
||||
scale: 1.4,
|
||||
anchor: new window.google.maps.Point(12, 24)
|
||||
};
|
||||
|
||||
// Auto fit bounds
|
||||
useEffect(() => {
|
||||
if (!mapRef.current || routePath.length === 0) return;
|
||||
|
||||
const bounds = new window.google.maps.LatLngBounds();
|
||||
routePath.forEach((p) => bounds.extend(p));
|
||||
mapRef.current.fitBounds(bounds);
|
||||
}, [routePath]);
|
||||
|
||||
if (!isLoaded) return <div>Loading map...</div>;
|
||||
|
||||
return (
|
||||
<GoogleMap mapContainerStyle={containerStyle} onLoad={(map) => (mapRef.current = map)} center={routePath[0]} zoom={16}>
|
||||
{/* Route line */}
|
||||
<Polyline
|
||||
path={routePath}
|
||||
options={{
|
||||
strokeColor: '#196fd2',
|
||||
strokeOpacity: 0.9,
|
||||
strokeWeight: 5
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Start marker */}
|
||||
<Marker
|
||||
position={routePath[0]}
|
||||
icon={{
|
||||
url: 'http://maps.google.com/mapfiles/ms/icons/green-dot.png'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* End marker */}
|
||||
<Marker position={routePath[routePath.length - 1]} icon={bikeIcon} />
|
||||
</GoogleMap>
|
||||
);
|
||||
}
|
||||
173
src/pages/nearle/reports/mapWithRoute.js
Normal file
173
src/pages/nearle/reports/mapWithRoute.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { MapContainer, TileLayer, Marker, Polyline, Tooltip } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import dayjs from 'dayjs';
|
||||
import { Chip, Stack, Typography, Box } from '@mui/material';
|
||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import CircularLoader from 'components/CircularLoader';
|
||||
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
dayjs.extend(utc);
|
||||
|
||||
// Start marker
|
||||
const startIcon = new L.Icon({
|
||||
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
shadowSize: [41, 41]
|
||||
});
|
||||
|
||||
// End marker
|
||||
const endIcon = new L.Icon({
|
||||
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
shadowSize: [41, 41]
|
||||
});
|
||||
|
||||
const MapWithRoute = ({ coordinates, additionalProps, order, setMapOpen }) => {
|
||||
console.log('additionalProps', additionalProps);
|
||||
|
||||
const mapRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
const [routePoints, setRoutePoints] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Fit the map to bounds
|
||||
useEffect(() => {
|
||||
if (mapRef.current && coordinates.length > 0) {
|
||||
const bounds = [
|
||||
[Math.min(...coordinates.map((c) => c.lat)), Math.min(...coordinates.map((c) => c.lng))],
|
||||
[Math.max(...coordinates.map((c) => c.lat)), Math.max(...coordinates.map((c) => c.lng))]
|
||||
];
|
||||
mapRef.current.fitBounds(bounds);
|
||||
}
|
||||
}, [coordinates]);
|
||||
|
||||
// Fetch OSRM Route → REAL ROAD ROUTE
|
||||
useEffect(() => {
|
||||
const getOSRMRoute = async () => {
|
||||
setLoading(true);
|
||||
|
||||
// FIX: If only one coordinate, stop loader and exit
|
||||
if (coordinates.length < 2) {
|
||||
setRoutePoints([]); // no route
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const start = coordinates[0];
|
||||
const end = coordinates[coordinates.length - 1];
|
||||
|
||||
const url = `https://router.project-osrm.org/route/v1/driving/${start.lng},${start.lat};${end.lng},${end.lat}?overview=full&geometries=geojson`;
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.routes?.length) {
|
||||
const points = data.routes[0].geometry.coordinates.map(([lng, lat]) => ({
|
||||
lat,
|
||||
lng
|
||||
}));
|
||||
setRoutePoints(points);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('OSRM Error:', err);
|
||||
} finally {
|
||||
setLoading(false); // ALWAYS STOP LOADING
|
||||
}
|
||||
};
|
||||
|
||||
getOSRMRoute();
|
||||
}, [coordinates]);
|
||||
|
||||
if (!coordinates || coordinates.length === 0) return null;
|
||||
|
||||
const start = coordinates[0];
|
||||
const end = coordinates[coordinates.length - 1];
|
||||
const center = coordinates[Math.floor(coordinates.length / 2)];
|
||||
|
||||
const InfoItem = ({ label, value }) => (
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
|
||||
{label}:
|
||||
</Typography>
|
||||
<Chip label={value || '0.00'} color="primary" sx={{ fontWeight: 700 }} />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', height: '100vh', position: 'relative', overflow: 'hidden' }}>
|
||||
{loading && <CircularLoader />}
|
||||
{/* CLOSE BUTTON */}
|
||||
<Chip
|
||||
label="Close"
|
||||
icon={<CloseCircleOutlined style={{ fontSize: 18 }} />}
|
||||
onClick={() => setMapOpen(false)}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
right: 12,
|
||||
zIndex: 2000,
|
||||
bgcolor: theme.palette.error.main,
|
||||
color: '#fff',
|
||||
fontWeight: 600,
|
||||
borderRadius: '12px',
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
boxShadow: theme.shadows[4],
|
||||
cursor: 'pointer',
|
||||
'& .MuiChip-icon': { color: '#fff' }
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* MAP */}
|
||||
<MapContainer center={center} zoom={14} scrollWheelZoom style={{ height: '100%', width: '100%' }} ref={mapRef}>
|
||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||
|
||||
{/* START MARKER */}
|
||||
<Marker position={start} icon={startIcon}>
|
||||
<Tooltip direction="bottom">{`Pickup: ${dayjs(additionalProps.riderStart).format('DD-MM-YYYY hh:mm A')}`}</Tooltip>
|
||||
</Marker>
|
||||
|
||||
{/* END MARKER */}
|
||||
<Marker position={end} icon={endIcon}>
|
||||
<Tooltip direction="bottom">{`Drop: ${dayjs(additionalProps.riderEnd).format('DD-MM-YYYY hh:mm A')}`}</Tooltip>
|
||||
</Marker>
|
||||
|
||||
{/* REAL OSRM ROUTE */}
|
||||
{routePoints.length > 0 && <Polyline positions={routePoints} pathOptions={{ color: 'blue', weight: 5 }} />}
|
||||
</MapContainer>
|
||||
|
||||
{/* BOTTOM DETAILS */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
bgcolor: 'rgba(255,255,255,0.96)',
|
||||
p: 2,
|
||||
boxShadow: theme.shadows[3],
|
||||
zIndex: 1500
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" flexWrap="wrap" rowGap={1.5} columnGap={3} alignItems="center">
|
||||
<InfoItem label="Tenant" value={order?.tenantname} />
|
||||
<InfoItem label="Rider" value={order?.ridername} />
|
||||
<InfoItem label="Pickup" value={order?.pickupcustomer} />
|
||||
<InfoItem label="Drop" value={order?.deliverycustomer} />
|
||||
<InfoItem label="Kms" value={order?.kms} />
|
||||
<InfoItem label="Actual Kms" value={order?.actualkms} />
|
||||
<InfoItem label="Rider Kms" value={order?.riderkms} />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapWithRoute;
|
||||
1301
src/pages/nearle/reports/ordersDetails.js
Normal file
1301
src/pages/nearle/reports/ordersDetails.js
Normal file
File diff suppressed because it is too large
Load Diff
935
src/pages/nearle/reports/ordersSummary.js
Normal file
935
src/pages/nearle/reports/ordersSummary.js
Normal file
@@ -0,0 +1,935 @@
|
||||
import { React, useState, useEffect, useRef } from 'react';
|
||||
import TitleCard from 'pages/titleCard';
|
||||
import axios from 'axios';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Empty } from 'antd';
|
||||
// material-ui
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Stack,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Chip,
|
||||
Autocomplete,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||
import dayjs from 'dayjs';
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
dayjs.extend(utc);
|
||||
import { CalendarMonth } from '@mui/icons-material';
|
||||
import { getreportlocationsummary, getreportsummary, gettenantlocations, getTenants } from 'pages/api/api';
|
||||
import MainCard from 'components/MainCard';
|
||||
import Loader from 'components/Loader';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import DateFilterDialog from 'components/DateFilterDialog';
|
||||
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
|
||||
|
||||
function formatNumberToRupees(value) {
|
||||
return new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: 'INR',
|
||||
minimumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
// ==============================|| MUI TABLE - ENHANCED ||============================== //
|
||||
|
||||
export default function OrdersReport() {
|
||||
// const [rows, setRows] = useState([]);
|
||||
const theme = useTheme();
|
||||
const locationRef = useRef(null);
|
||||
const tenantRef = useRef(null);
|
||||
const [appId, setAppId] = useState(0);
|
||||
const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||
const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||
const [locaName, setLocoName] = useState('All');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openRow, setOpenRow] = useState(null);
|
||||
const [datestatus, setDatestatus] = useState('Today');
|
||||
const [total, settotal] = useState(0);
|
||||
const [totalOrders, settotalOrders] = useState(0);
|
||||
const [totalOrderPend, setTotalOrderPend] = useState(0);
|
||||
const [totalOrderComplete, setTotalOrderComplete] = useState(0);
|
||||
const [totalOrderCancel, setTotalOrderCancel] = useState(0);
|
||||
const [totalDeliPend, setTotalDeliPend] = useState(0);
|
||||
const [totalDeliComplete, setTotalDeliComplete] = useState(0);
|
||||
const [totalDeliCancel, setTotalDeliCancel] = useState(0);
|
||||
const [ridersdata, setRidersdata] = useState([]);
|
||||
const userid = localStorage.getItem('userid');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [locationid, setLocationid] = useState(0);
|
||||
const [tenantid, setTenantid] = useState(0);
|
||||
const [tenantValue, setTenantValue] = useState(null);
|
||||
const [locationValue, setLocationValue] = useState(null);
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('openRow', openRow);
|
||||
}, [openRow]);
|
||||
|
||||
// to clear the tenant and location and rider autocomplete
|
||||
useEffect(() => {
|
||||
setTenantid(0);
|
||||
setTenantValue(null);
|
||||
setLocationid(0);
|
||||
setLocationValue(null);
|
||||
setOpenRow(null);
|
||||
}, [appId]);
|
||||
// to clear the location and rider autocomplete
|
||||
useEffect(() => {
|
||||
setLocationid(0);
|
||||
setLocationValue(null);
|
||||
setOpenRow(null);
|
||||
}, [tenantid]);
|
||||
|
||||
// table header
|
||||
const headCells = [
|
||||
{
|
||||
id: 's.no',
|
||||
numeric: false,
|
||||
disablePadding: true,
|
||||
label: '#',
|
||||
rowSpan: 2,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f3e5f5'
|
||||
},
|
||||
{
|
||||
id: 'clients',
|
||||
numeric: 'true',
|
||||
disablePadding: false,
|
||||
label: tenantid ? 'Location' : 'Tenant',
|
||||
rowSpan: 2,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f3e5f5'
|
||||
},
|
||||
{
|
||||
id: 'all',
|
||||
numeric: 'center',
|
||||
disablePadding: false,
|
||||
label: 'All',
|
||||
rowSpan: 2,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f3e5f5'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'orders',
|
||||
numeric: 'center',
|
||||
disablePadding: false,
|
||||
label: 'orders',
|
||||
rowSpan: 1,
|
||||
colSpan: 3,
|
||||
bgcolor: '#ffcdd2'
|
||||
},
|
||||
{
|
||||
id: 'deliveries',
|
||||
numeric: 'center',
|
||||
disablePadding: false,
|
||||
label: 'deliveries',
|
||||
rowSpan: 1,
|
||||
colSpan: 3,
|
||||
bgcolor: '#f8bbd0'
|
||||
},
|
||||
{
|
||||
id: 'Charges',
|
||||
numeric: 'center',
|
||||
disablePadding: false,
|
||||
label: 'Collection Amt',
|
||||
rowSpan: 2,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f3e5f5'
|
||||
},
|
||||
{
|
||||
id: 'kilometer',
|
||||
numeric: 'center',
|
||||
disablePadding: false,
|
||||
label: 'Kms/Actual Kms',
|
||||
rowSpan: 2,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f3e5f5'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'Amount',
|
||||
numeric: 'center',
|
||||
disablePadding: true,
|
||||
label: 'Amount',
|
||||
rowSpan: 2,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f3e5f5'
|
||||
},
|
||||
{
|
||||
id: 'action',
|
||||
numeric: 'center',
|
||||
disablePadding: true,
|
||||
label: 'Action',
|
||||
rowSpan: 2,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f3e5f5'
|
||||
}
|
||||
];
|
||||
|
||||
const headCells1 = [
|
||||
{
|
||||
id: 'pending',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'pending',
|
||||
rowSpan: 1,
|
||||
colSpan: 1,
|
||||
bgcolor: '#ffcdd2'
|
||||
},
|
||||
{
|
||||
id: 'cancelled',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'cancelled',
|
||||
rowSpan: 1,
|
||||
colSpan: 1,
|
||||
bgcolor: '#ffcdd2'
|
||||
},
|
||||
{
|
||||
id: 'completed',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'completed',
|
||||
rowSpan: 1,
|
||||
colSpan: 1,
|
||||
bgcolor: '#ffcdd2'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'pending',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'pending',
|
||||
rowSpan: 1,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f8bbd0'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'cancelled',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'cancelled',
|
||||
rowSpan: 1,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f8bbd0'
|
||||
},
|
||||
{
|
||||
id: 'completed',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'completed',
|
||||
rowSpan: 1,
|
||||
colSpan: 1,
|
||||
bgcolor: '#f8bbd0'
|
||||
}
|
||||
];
|
||||
const getColorByValue = (value) => {
|
||||
return Number(value) !== 0 ? 'red' : 'inherit';
|
||||
};
|
||||
const coloredCell = (value) => <Typography sx={{ color: getColorByValue(value) }}>{value}</Typography>;
|
||||
|
||||
// ==============================|| MUI TABLE - HEADER ||============================== //
|
||||
|
||||
function EnhancedTableHead() {
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headCells.map((headCell) => (
|
||||
<TableCell
|
||||
key={headCell.id}
|
||||
align={headCell.numeric == 'center' ? 'center' : headCell.numeric == 'true' ? 'left' : 'right'}
|
||||
padding={headCell.disablePadding ? 'none' : 'normal'}
|
||||
rowSpan={headCell.rowSpan}
|
||||
colSpan={headCell.colSpan}
|
||||
sx={{ bgcolor: headCell.bgcolor }}
|
||||
>
|
||||
{headCell.label}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
{headCells1.map((headCell) => (
|
||||
<TableCell
|
||||
key={headCell.id}
|
||||
align={headCell.numeric ? 'right' : 'left'}
|
||||
padding={headCell.disablePadding ? 'none' : 'normal'}
|
||||
rowSpan={headCell.rowSpan}
|
||||
colSpan={headCell.colSpan}
|
||||
sx={{ bgcolor: headCell.bgcolor }}
|
||||
>
|
||||
{headCell.label}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
}
|
||||
|
||||
// ==============================|| fetchOrdersSummary (orders summary)||============================== //
|
||||
const {
|
||||
data: rows,
|
||||
isLoading: isLoadingReports,
|
||||
isError: isErrorReports, //true or false
|
||||
error: reportsError
|
||||
} = useQuery({
|
||||
queryKey: [appId, tenantid, locationid, startdate, enddate],
|
||||
queryFn: tenantid ? getreportlocationsummary : getreportsummary
|
||||
});
|
||||
// ==============================|| getTenants ||============================== //
|
||||
|
||||
const {
|
||||
data: tenantlist
|
||||
} = useQuery({
|
||||
queryKey: ['tenantlist', appId],
|
||||
queryFn: () => getTenants(appId), // Ensure appId is passed
|
||||
enabled: appId !== 0 // Ensures query runs only when appId is valid
|
||||
});
|
||||
// ==============================|| gettenantlocations ||============================== //
|
||||
|
||||
const {
|
||||
data: locationlist
|
||||
} = useQuery({
|
||||
queryKey: ['gettenantlocations', tenantid],
|
||||
queryFn: () => gettenantlocations(tenantid), // Ensure appId is passed
|
||||
enabled: tenantid !== 0 // Ensures query runs only when appId is valid
|
||||
});
|
||||
// ==============================|| fetchridersummary by tenid (orders summary)||============================== //
|
||||
|
||||
// ==============================|| getuserreportsummary by tenid (orders summary)||============================== //
|
||||
const getuserreportsummary = async (tenantid) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const riderRes = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/deliveries/getuserreportsummary/?&tenantid=${tenantid}&fromdate=${startdate}&todate=${enddate}`
|
||||
);
|
||||
console.log('riderRes', riderRes.data.details);
|
||||
setRidersdata(riderRes.data.details);
|
||||
} catch (error) {
|
||||
console.log('riderRes', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// ==============================|| getriderlocationsummary by tenid (orders summary)||============================== //
|
||||
const getriderlocationsummary = async (id) => {
|
||||
try {
|
||||
const riderRes = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/deliveries/getriderlocationsummary/?&tenantid=${tenantid}&locationid=${id}&fromdate=${startdate}&todate=${enddate}`
|
||||
);
|
||||
console.log('riderRes', riderRes.data.details);
|
||||
setRidersdata(riderRes.data.details);
|
||||
} catch (error) {
|
||||
console.log('riderRes', error);
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| calculate||============================== //
|
||||
const calculate = () => {
|
||||
let calculatedTotal = 0;
|
||||
let ordersTotal = 0;
|
||||
let Orderpending = 0;
|
||||
let OrderComplete = 0;
|
||||
let OrderCancel = 0;
|
||||
let deliverypending = 0;
|
||||
let deliverycomplete = 0;
|
||||
let deliverycancel = 0;
|
||||
rows &&
|
||||
rows.forEach((row) => {
|
||||
calculatedTotal += row.charges;
|
||||
ordersTotal += row.totalorders;
|
||||
Orderpending += row.Orderspending;
|
||||
OrderComplete += row.orderscompleted;
|
||||
OrderCancel += row.orderscancelled;
|
||||
deliverypending += row.deliveriespending;
|
||||
deliverycomplete += row.deliveriescompleted;
|
||||
deliverycancel += row.deliveriescancelled;
|
||||
});
|
||||
// Update the state after the calculation is done
|
||||
settotal(calculatedTotal);
|
||||
settotalOrders(ordersTotal);
|
||||
setTotalOrderPend(Orderpending);
|
||||
setTotalOrderComplete(OrderComplete);
|
||||
setTotalOrderCancel(OrderCancel);
|
||||
setTotalDeliPend(deliverypending);
|
||||
setTotalDeliComplete(deliverycomplete);
|
||||
setTotalDeliCancel(deliverycancel);
|
||||
};
|
||||
useEffect(() => {
|
||||
calculate();
|
||||
}, [rows]);
|
||||
|
||||
// ==============================|| fetchAppLocations ||============================== //
|
||||
const fetchAppLocations = async () => {
|
||||
try {
|
||||
const locationRes = await axios.get(`${process.env.REACT_APP_URL}/partners/getlocations/?userid=${userid}`);
|
||||
const updatedLocations = [
|
||||
...locationRes.data.details,
|
||||
{ locationname: 'All', applocationid: 0 } // Add your new object here
|
||||
];
|
||||
console.log('fetchAppLocations', updatedLocations);
|
||||
setLocations(updatedLocations);
|
||||
} catch (err) {
|
||||
console.log('locationRes', err);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchAppLocations();
|
||||
}, []);
|
||||
|
||||
if (isErrorReports) console.log('An error has occurred:(isErrorReports) ' + reportsError.message);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(loading || isLoadingReports) && (
|
||||
<>
|
||||
{/* <CircularLoader /> */}
|
||||
<Loader />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* <Backdrop
|
||||
sx={{
|
||||
color: '#fff',
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1
|
||||
}}
|
||||
open={loading || isLoadingReports} // when loader = true, backdrop covers the page
|
||||
>
|
||||
<CircularLoader color="inherit" />
|
||||
</Backdrop> */}
|
||||
|
||||
<TitleCard
|
||||
title="Orders Summary"
|
||||
secondary={
|
||||
<LocationAutocomplete
|
||||
ref={locationRef}
|
||||
locaName={locaName}
|
||||
setAppId={setAppId}
|
||||
setLocoName={setLocoName}
|
||||
setPage={setPage}
|
||||
sx={{ minWidth: 280, width: '100%', flex: { custom550: 0 } }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Stack
|
||||
display={'flex'}
|
||||
flexDirection={'row'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
flexWrap={'wrap'}
|
||||
gap={1}
|
||||
sx={{ border: '1px solid', borderColor: 'bg.main', p: 2 }}
|
||||
>
|
||||
<Stack>
|
||||
{startdate && enddate && (
|
||||
<Stack direction="row" gap={2} flexWrap={'wrap'}>
|
||||
<Chip label={`Orders-${datestatus}`} color="primary" variant="light" size="small" />
|
||||
<Tooltip title="Date Filter" placement="top">
|
||||
<Chip
|
||||
label={
|
||||
<Typography noWrap color="secondary">
|
||||
{dayjs(startdate).format('DD/MM/YYYY')} - {dayjs(enddate).format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
}
|
||||
variant="combined"
|
||||
color="warning"
|
||||
size="small"
|
||||
deleteIcon={<CalendarMonth style={{ fontSize: 18 }} />}
|
||||
onDelete={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
onClick={() => setOpen(true)}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
<Stack
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 2,
|
||||
flexWrap: 'wrap',
|
||||
flexGrow: 1,
|
||||
justifyContent: { xs: 'start', custom950: 'right' }
|
||||
}}
|
||||
>
|
||||
<Autocomplete
|
||||
options={tenantlist || []}
|
||||
value={tenantValue}
|
||||
sx={{ minWidth: 250, flex: { xs: 1, custom950: 0 } }}
|
||||
onOpen={(event) => {
|
||||
if (!appId) {
|
||||
event.preventDefault();
|
||||
OpenToast('Please select a your app location first!', 'warning', 3000);
|
||||
setTimeout(() => {
|
||||
locationRef.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
}}
|
||||
onChange={(e, val, reason) => {
|
||||
if (reason === 'clear') {
|
||||
setTenantid(0);
|
||||
setTenantValue(null);
|
||||
setLocationid(0);
|
||||
setLocationValue(null);
|
||||
} else {
|
||||
setTenantid(val?.tenantid || 0);
|
||||
setTenantValue(val);
|
||||
setLocationid(val.locationid);
|
||||
setLocationValue(null);
|
||||
}
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} inputRef={tenantRef} label="Select Tenant" />}
|
||||
/>
|
||||
{/* ==================================================== || Location Autocomplete || ==================================================== */}
|
||||
<Autocomplete
|
||||
options={locationlist || []}
|
||||
getOptionLabel={(option) => `${option.locationname} (${option.suburb})` || ''}
|
||||
value={locationValue}
|
||||
sx={{ minWidth: 250, flex: { xs: 1, custom950: 0 } }}
|
||||
onOpen={(event) => {
|
||||
if (!appId && !tenantid) {
|
||||
event.preventDefault();
|
||||
OpenToast('Please select a your Location and Tenant first!', 'warning', 3000);
|
||||
setTimeout(() => {
|
||||
locationRef.current?.focus();
|
||||
}, 0);
|
||||
} else if (!tenantid) {
|
||||
event.preventDefault();
|
||||
OpenToast('Please select a your Tenant first!', 'warning', 3000);
|
||||
setTimeout(() => {
|
||||
tenantRef.current?.focus();
|
||||
}, 0);
|
||||
}
|
||||
}}
|
||||
onChange={(e, val, reason) => {
|
||||
if (reason === 'clear') {
|
||||
setLocationid(0);
|
||||
setLocationValue(null);
|
||||
} else {
|
||||
setLocationid(val.locationid || 0);
|
||||
setLocationValue(val);
|
||||
}
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} label="Select Location" />}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<MainCard content={false}>
|
||||
<TableContainer
|
||||
sx={{
|
||||
overflow: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '12px', // scroll bar width
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: theme.palette.primary.main, // thumb color
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
backgroundColor: theme.palette.primary.dark, // hover color
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
backgroundColor: theme.palette.primary.lighter,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Table>
|
||||
{/* ============================================ ||EnhancedTableHead || ============================================ */}
|
||||
|
||||
<EnhancedTableHead />
|
||||
{/* ============================================ || TableBody || ============================================ */}
|
||||
|
||||
<TableBody>
|
||||
{isLoadingReports && <OrdersTableSkeleton col={8} />}
|
||||
{rows?.length == 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={13} rowSpan={40}>
|
||||
<Stack width={'100%'} direction={'row'} justifyContent={'center'}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{(rows?.length != 0 || rows) &&
|
||||
rows?.map((row, index) => (
|
||||
<>
|
||||
{/* ============================================ || tablerow 1 || ============================================ */}
|
||||
|
||||
<TableRow
|
||||
key={row.tenantname || row.locationname}
|
||||
// sx={{
|
||||
// cursor: openRow === row.tenantname ? 'pointer' : null
|
||||
// }}
|
||||
>
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack spacing={1}>
|
||||
<Typography sx={{ whiteSpace: 'noWrap' }}>{tenantid ? row.locationname : row.tenantname} </Typography>
|
||||
<Typography variant="subtitle2">Id : {tenantid ? row.locationid : row.tenantid} </Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.totalorders)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.Orderspending)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.orderscancelled)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.orderscompleted)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriespending)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriescancelled)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriescompleted)}</TableCell>
|
||||
<TableCell align="center">
|
||||
{/* <Tooltip title="Cash on Delivery" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={`₹${Number(row.payondelivery).toFixed(2)}`}
|
||||
variant="combined"
|
||||
color={row.payondelivery ? 'error' : 'secondary'}
|
||||
sx={{
|
||||
mb: 1,
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<br /> */}
|
||||
{/* <Tooltip title="Pay Later" placement="bottom"> */}
|
||||
<Chip
|
||||
size="small"
|
||||
label={`₹${Number(row.collectionamt).toFixed(2)}`}
|
||||
variant="combined"
|
||||
color={row.collectionamt ? 'warning' : 'secondary'}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
{/* </Tooltip> */}
|
||||
</TableCell>{' '}
|
||||
<TableCell align="center">
|
||||
<Tooltip title="kms" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={`${Number(row.kms).toFixed(2)}`}
|
||||
color={row.kms ? 'error' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
mb: 1,
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<br />
|
||||
<Tooltip title="Actual kms" placement="bottom">
|
||||
<Chip
|
||||
size="small"
|
||||
label={Number(row.cumulativekms).toFixed(2)}
|
||||
color={row.cumulativekms ? 'success' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Tooltip title="Delivery Amount" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={formatNumberToRupees(row.charges >= row.deliveryamt ? row.charges : row.deliveryamt)}
|
||||
color={row.deliveryamt || row.charges ? 'primary' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 100
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<IconButton
|
||||
aria-label="expand row"
|
||||
size="small"
|
||||
// onClick={() => {
|
||||
// setRidersdata([]); // yo avoid appending new data list to exiting
|
||||
// setTimeout(() => {
|
||||
// openRow !== row.tenantname && (tenantid
|
||||
// ? getriderlocationsummary(row.locationid)
|
||||
// : getuserreportsummary(row.tenantid));
|
||||
// setOpenRow(openRow === row.tenantname ? null : row.tenantname);
|
||||
// }, 0);
|
||||
// }}
|
||||
onClick={() => {
|
||||
if (tenantid) {
|
||||
// if tenant selected and shows the location list
|
||||
const isOpening = openRow !== row.tenantname;
|
||||
// toggle row
|
||||
setOpenRow(isOpening ? row.tenantname : null);
|
||||
if (!isOpening) return; // ❌ closing → don't call API
|
||||
// clear old data only when opening
|
||||
setRidersdata([]);
|
||||
// ✅ call correct API
|
||||
getriderlocationsummary(row.locationid);
|
||||
} else {
|
||||
const isOpening = openRow !== row.locationname;
|
||||
|
||||
// toggle row
|
||||
setOpenRow(isOpening ? row.locationname : null);
|
||||
|
||||
if (!isOpening) return; // ❌ closing → don't call API
|
||||
|
||||
// clear old data only when opening
|
||||
setRidersdata([]);
|
||||
|
||||
// ✅ call correct API
|
||||
|
||||
getuserreportsummary(row.tenantid);
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: openRow === row.tenantname ? 'primary.main' : null,
|
||||
color: openRow === row.tenantname ? 'white' : null,
|
||||
'&:hover': {
|
||||
bgcolor: openRow === row.tenantname ? 'primary.main' : '#e1bee7'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip title="View Riders" placement="top">
|
||||
{tenantid ? (
|
||||
openRow === row.tenantname ? (
|
||||
<KeyboardArrowUpIcon />
|
||||
) : (
|
||||
<KeyboardArrowDownIcon />
|
||||
)
|
||||
) : openRow === row.locationname ? (
|
||||
<KeyboardArrowUpIcon />
|
||||
) : (
|
||||
<KeyboardArrowDownIcon />
|
||||
)}
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/* ============================================ || collapsive row || ============================================ */}
|
||||
|
||||
{(tenantid ? openRow === row.tenantname : openRow === row.locationname) && (
|
||||
<TableRow
|
||||
// sx={{
|
||||
// cursor: openRow === row.tenantname ? 'pointer' : null,
|
||||
// '&:hover': {
|
||||
// bgcolor: 'white!important'
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={15}>
|
||||
<Box sx={{ margin: 1, border: '1px solid', borderColor: 'secondary.lighter' }}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow sx={{ bgcolor: 'primary.lighter' }}>
|
||||
<TableCell>#</TableCell>
|
||||
<TableCell>Rider</TableCell>
|
||||
<TableCell align="center">Orders</TableCell>
|
||||
<TableCell align="center">Deliveries</TableCell>
|
||||
<TableCell align="center">Pending</TableCell>
|
||||
<TableCell align="center">Cancelled</TableCell>
|
||||
<TableCell align="center">Completed</TableCell>
|
||||
<TableCell align="center">Collection Amt</TableCell>
|
||||
<TableCell align="center">kms/Actual kms</TableCell>
|
||||
<TableCell align="center">Charges</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading && <OrdersTableSkeleton col={5} />}
|
||||
{/* {ridersdata?.length == 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={13} rowSpan={40}>
|
||||
<Stack width={'100%'} direction={'row'} justifyContent={'center'}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)} */}
|
||||
{ridersdata?.map((row, index) => (
|
||||
<TableRow key={row.tenantname}>
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack>
|
||||
<Typography sx={{ whiteSpace: 'noWrap' }}>{` ${row.firstname} ${row.lastname}`}</Typography>
|
||||
<Typography variant="subtitle2" sx={{ whiteSpace: 'noWrap' }}>
|
||||
{row.ridercontact}
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" sx={{ whiteSpace: 'noWrap' }}>
|
||||
ID : {row.userid}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">{coloredCell(row.orderscreated)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.totalorders)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriespending)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriescancelled)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriescompleted)}</TableCell>
|
||||
{/* <TableCell align="center">{coloredCell(row.picked)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.active)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.skipped)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.cancelled)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.delivered)}</TableCell> */}
|
||||
<TableCell align="center">
|
||||
{/* <Tooltip title="Cash on Delivery" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={formatNumberToRupees(row.payondelivery)}
|
||||
color={row.payondelivery ? 'error' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
mb: 1,
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<br /> */}
|
||||
{/* <Tooltip title="Paylater" placement="top"> */}
|
||||
<Chip
|
||||
size="small"
|
||||
label={formatNumberToRupees(row.collectionamt)}
|
||||
color={row.collectionamt ? 'warning' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
{/* </Tooltip> */}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Tooltip title="kms" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={row.kms.toFixed(2)}
|
||||
color={row.kms ? 'error' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
mb: 1,
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<br />
|
||||
<Tooltip title="Actual kms" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={row.cumulativekms.toFixed(2)}
|
||||
color={row.cumulativekms ? 'success' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Tooltip title="Delivery Amount" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={formatNumberToRupees(row.charges >= row.deliveryamt ? row.charges : row.deliveryamt)}
|
||||
color={row.deliveryamt || row.charges ? 'primary' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 100
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
|
||||
{rows?.length != 0 && rows && (
|
||||
<TableRow sx={{}}>
|
||||
<TableCell colSpan={2}>
|
||||
<Typography variant="h5">Total</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="h5">{totalOrders}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="h5">{totalOrderPend}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="h5"> {totalOrderCancel}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="h5">{totalOrderComplete}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="h5">{totalDeliPend}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="h5"> {totalDeliCancel}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Typography variant="h5">{totalDeliComplete}</Typography>
|
||||
</TableCell>
|
||||
<TableCell></TableCell>
|
||||
<TableCell></TableCell>
|
||||
<TableCell align="right" sx={{ pr: -2 }}>
|
||||
<Typography variant="h5">{formatNumberToRupees(total)}</Typography>
|
||||
</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Divider />
|
||||
</MainCard>
|
||||
{/* ============================================ || date filter dialog || ============================================ */}
|
||||
<DateFilterDialog
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onSelect={(range) => {
|
||||
setStartdate(range.startDate);
|
||||
setEnddate(range.endDate);
|
||||
setDatestatus(range.label);
|
||||
console.log('Selected Date Range:', range);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
296
src/pages/nearle/reports/ridersLogs.js
Normal file
296
src/pages/nearle/reports/ridersLogs.js
Normal file
@@ -0,0 +1,296 @@
|
||||
import React, { useState, useEffect, Fragment } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Drawer,
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
AppBar,
|
||||
useMediaQuery,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
useTheme,
|
||||
ListItemAvatar,
|
||||
Stack,
|
||||
Button,
|
||||
Checkbox,
|
||||
Skeleton
|
||||
} from '@mui/material';
|
||||
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import SearchBar from 'components/nearle_components/SearchBar';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchRidersLogs } from 'pages/api/api';
|
||||
import RiderLocationMap from './RiderLocationMap';
|
||||
import MainCard from 'components/MainCard';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import error500 from 'assets/images/maintenance/Error500.png';
|
||||
|
||||
const drawerWidth = 350;
|
||||
|
||||
const RidersLogs = () => {
|
||||
const theme = useTheme();
|
||||
const isDesktop = useMediaQuery('(min-width:900px)');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedRiders, setSelectedRiders] = useState([]);
|
||||
const [riderSearch, setRiderSearch] = useState('');
|
||||
const appId = 1;
|
||||
const {
|
||||
data: riders,
|
||||
isLoading: ridersIsLoading,
|
||||
isFetching: riderIsFetching,
|
||||
refetch: riderLogsRefetch,
|
||||
error: riderLogsError
|
||||
} = useQuery({
|
||||
queryKey: [appId, dayjs().format('YYYY-MM-DD'), riderSearch],
|
||||
queryFn: fetchRidersLogs,
|
||||
refetchInterval: 5 * 60 * 1000
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// const sortedRiders = riders?.sort((a, b) => a.firstname.localeCompare(b.firstname));
|
||||
setSelectedRiders(riders);
|
||||
}, [riders]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('selectedRiders', selectedRiders);
|
||||
}, [selectedRiders]);
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(isDesktop);
|
||||
}, [isDesktop]);
|
||||
|
||||
return (
|
||||
<MainCard content={false}>
|
||||
<Box sx={{ display: 'flex', width: '100%', height: '100%', position: 'relative' }}>
|
||||
{/* Drawer */}
|
||||
<Drawer
|
||||
variant={isDesktop ? 'persistent' : 'temporary'}
|
||||
open={open}
|
||||
onClose={() => !isDesktop && setOpen(false)}
|
||||
ModalProps={{ keepMounted: true }}
|
||||
sx={{
|
||||
'& .MuiDrawer-paper': {
|
||||
width: drawerWidth,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
height: '100%',
|
||||
overflowY: 'auto',
|
||||
transition: 'transform 0.35s ease-in-out',
|
||||
zIndex: 13
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Search */}
|
||||
<Box sx={{ position: 'sticky', top: 0, zIndex: 1 }}>
|
||||
<SearchBar
|
||||
value={riderSearch}
|
||||
placeholder="Search Rider"
|
||||
onChange={(e) => setRiderSearch(e.target.value)}
|
||||
sx={{
|
||||
height: 60,
|
||||
bgcolor: 'white',
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette.secondary.light
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<List>
|
||||
<ListItem sx={{ cursor: 'pointer', '&:hover': { bgcolor: theme.palette.secondary.lighter }, bgcolor: 'white', mt: -1 }}>
|
||||
<ListItemAvatar>
|
||||
<Checkbox
|
||||
checked={riders?.length == selectedRiders?.length}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedRiders(riders);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary="All" />
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</List>
|
||||
</Box>
|
||||
{/* Rider List */}
|
||||
<List>
|
||||
{/* Individuals */}
|
||||
{ridersIsLoading || riderIsFetching
|
||||
? Array.from({ length: 10 }).map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
<ListItem sx={{ py: 1.5, px: 2 }}>
|
||||
<ListItemAvatar>
|
||||
<Skeleton variant="circular" width={24} height={24} />
|
||||
</ListItemAvatar>
|
||||
|
||||
<ListItemText
|
||||
primary={<Skeleton variant="text" width="60%" height={22} />}
|
||||
secondary={<Skeleton variant="text" width="40%" height={18} />}
|
||||
/>
|
||||
|
||||
<Stack spacing={0.5} textAlign="right">
|
||||
<Skeleton variant="text" width={50} height={18} />
|
||||
<Skeleton variant="text" width={80} height={16} />
|
||||
</Stack>
|
||||
</ListItem>
|
||||
|
||||
<Divider />
|
||||
</Fragment>
|
||||
))
|
||||
: riders?.map((row) => {
|
||||
return (
|
||||
<Fragment key={row.userid}>
|
||||
<ListItem
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
py: 1,
|
||||
px: 2,
|
||||
borderRadius: 1,
|
||||
'&:hover': { bgcolor: theme.palette.secondary.lighter }
|
||||
}}
|
||||
secondaryAction={
|
||||
<Stack textAlign="right" spacing={0.5}>
|
||||
<Typography variant="body2" noWrap sx={{ color: row.status == 'active' ? 'success.main' : 'error.main' }}>
|
||||
{row.userid}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" noWrap>
|
||||
{dayjs(row.logdate).format('DD/MM/YYYY hh:mm A')}
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<ListItemAvatar>
|
||||
<Checkbox
|
||||
sx={{
|
||||
color: row.status == 'active' ? 'green' : 'red',
|
||||
'&.Mui-checked': {
|
||||
color: row.status == 'active' ? 'green' : 'red'
|
||||
}
|
||||
}}
|
||||
checked={
|
||||
// INDIVIDUAL CHECKED CONDITION
|
||||
selectedRiders?.length === 1 && selectedRiders[0]?.userid === row?.userid
|
||||
}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
// SELECT ONE RIDER
|
||||
setSelectedRiders([row]);
|
||||
} else {
|
||||
// UNCHECK -> SELECT ALL
|
||||
setSelectedRiders(riders);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItemAvatar>
|
||||
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography noWrap>
|
||||
{row.username?.slice(0, 25) || ''}
|
||||
{row.username?.length > 25 && '...'}
|
||||
|
||||
{/* {row.status === 'active' && <TaskAltIcon fontSize="small" color="success" sx={{ ml: 1 }} />} */}
|
||||
</Typography>
|
||||
}
|
||||
secondary={
|
||||
<Typography variant="caption" color="text.secondary" noWrap>
|
||||
{row.contactno || '##########'}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<Divider />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Drawer>
|
||||
|
||||
{/* AppBar */}
|
||||
<AppBar
|
||||
elevation={0}
|
||||
position="absolute"
|
||||
sx={{
|
||||
top: 0,
|
||||
left: open && isDesktop ? `${drawerWidth}px` : 0,
|
||||
width: open && isDesktop ? `calc(100% - ${drawerWidth}px)` : '100%',
|
||||
transition: 'left 0.3s ease, width 0.3s ease',
|
||||
backgroundColor: 'white',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette.secondary.light
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ width: '100%' }}>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<IconButton color="primary" onClick={() => setOpen(!open)}>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
|
||||
<Typography variant="h5" color="primary" sx={{ ml: 2 }}>
|
||||
Riders Locations
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
riderLogsRefetch();
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
{/* Map */}
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
overflow: 'auto',
|
||||
pt: '64px',
|
||||
pl: open && isDesktop ? `${drawerWidth}px` : 0,
|
||||
transition: 'padding-left 0.3s ease',
|
||||
minHeight: '80vh'
|
||||
}}
|
||||
>
|
||||
{(ridersIsLoading || riderIsFetching) && (
|
||||
<Box position="relative" width="100%" height="80vh" display="grid" placeItems="center">
|
||||
{/* <CircularLoader /> */}
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
width="100%"
|
||||
height="100%"
|
||||
animation="wave"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
borderRadius: 1,
|
||||
zIndex: 1
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{selectedRiders?.length > 0 && <RiderLocationMap riderLocations={selectedRiders} />}
|
||||
{riderLogsError && (
|
||||
<Box sx={{ width: '100% ', height: '100%' }}>
|
||||
<img src={error500} alt="mantis" style={{ height: '100%', width: '100%' }} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</MainCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default RidersLogs;
|
||||
454
src/pages/nearle/reports/ridersSummary.js
Normal file
454
src/pages/nearle/reports/ridersSummary.js
Normal file
@@ -0,0 +1,454 @@
|
||||
import { React, useState, useEffect } from 'react';
|
||||
import TitleCard from 'pages/titleCard';
|
||||
import axios from 'axios';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetchRidersSummary } from 'pages/api/api';
|
||||
|
||||
import { Empty } from 'antd';
|
||||
// material-ui
|
||||
import {
|
||||
Divider,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
Stack,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Chip,
|
||||
Collapse,
|
||||
Dialog,
|
||||
DialogContent
|
||||
} from '@mui/material';
|
||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
||||
import { IoLocationOutline } from 'react-icons/io5';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
dayjs.extend(utc);
|
||||
import { CalendarMonth } from '@mui/icons-material';
|
||||
import MainCard from 'components/MainCard';
|
||||
import Loader from 'components/Loader';
|
||||
import DateFilterDialog from 'components/DateFilterDialog';
|
||||
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
|
||||
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
|
||||
import RidersRoutes from './RidersRoutes';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
|
||||
function formatNumberToRupees(value) {
|
||||
return new Intl.NumberFormat('en-IN', {
|
||||
style: 'currency',
|
||||
currency: 'INR',
|
||||
minimumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
const getColorByValue = (value) => {
|
||||
return Number(value) !== 0 ? 'red' : 'inherit';
|
||||
};
|
||||
const coloredCell = (value) => <Typography sx={{ color: getColorByValue(value) }}>{value}</Typography>;
|
||||
|
||||
// ==============================|| MUI TABLE - ENHANCED ||============================== //
|
||||
|
||||
export default function RidersSummary() {
|
||||
const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||
const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD'));
|
||||
const [locaName, setLocoName] = useState('All');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [datestatus, setDatestatus] = useState('Today');
|
||||
const [total, settotal] = useState(0);
|
||||
const [tenantData, setTenantData] = useState([]);
|
||||
const [openRow, setOpenRow] = useState(null); // Initially no row is open
|
||||
// const [appId, setAppId] = useState(localStorage.getItem('applocationid'));
|
||||
const [appId, setAppId] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [mapOpen, setMapOpen] = useState(false);
|
||||
const [logDetails, setLogDetails] = useState(null);
|
||||
|
||||
// ==============================|| fetchRidersSummary (riders summary)||============================== //
|
||||
const { isLoading: isLoadingReports, data: rows } = useQuery({
|
||||
queryKey: ['ridersummary', appId, startdate, enddate],
|
||||
queryFn: fetchRidersSummary
|
||||
});
|
||||
|
||||
// ==============================|| fetchTenantSummary by rider (rider summary)||============================== //
|
||||
const fetchTenantSummary = async (riderUserid) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const tenantRes = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/deliveries/getreportsummary/?&fromdate=${startdate}&todate=${enddate}&userid=${riderUserid}`
|
||||
);
|
||||
console.log('tenantRes', tenantRes.data.details);
|
||||
setTenantData(tenantRes.data.details);
|
||||
} catch (error) {
|
||||
console.log('tenantRes', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| getuserdeliverylogs (rider summary)||============================== //
|
||||
|
||||
const getuserdeliverylogs = async (userid) => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${process.env.REACT_APP_URL}/deliveries/getuserdeliverylogs/?userid=${userid}&fromdate=2026-01-28&todate=2026-01-28 `
|
||||
);
|
||||
setLogDetails(response.data.details);
|
||||
} catch (err) {
|
||||
OpenToast(err?.message, 'error', 2000);
|
||||
console.log('getuserdeliverylogs', err.message);
|
||||
}
|
||||
};
|
||||
|
||||
// ==============================|| calculate||============================== //
|
||||
const calculate = async () => {
|
||||
let calculatedTotal = 0;
|
||||
rows &&
|
||||
rows.forEach((row, index) => {
|
||||
console.log(index, row.deliveryamt);
|
||||
calculatedTotal += row.deliveryamt;
|
||||
});
|
||||
// Update the state after the calculation is done
|
||||
settotal(calculatedTotal);
|
||||
console.log('calculatedTotal', calculatedTotal);
|
||||
};
|
||||
useEffect(() => {
|
||||
calculate();
|
||||
}, [rows]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(isLoadingReports || loading) && (
|
||||
<>
|
||||
<Loader />
|
||||
{/* <CircularLoader /> */}
|
||||
</>
|
||||
)}
|
||||
<TitleCard title="Riders Summary" />
|
||||
|
||||
<MainCard
|
||||
content={false}
|
||||
title={
|
||||
<Stack
|
||||
display={'flex'}
|
||||
flexDirection={'row'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
flexWrap={'wrap'}
|
||||
gap={1}
|
||||
sx={{ border: '1px solid', borderColor: 'bg.main', p: 2 }}
|
||||
>
|
||||
<Stack direction="column" alignItems="flex-start" spacing={1} sx={{}}>
|
||||
{startdate && enddate && (
|
||||
<Stack direction="row" spacing={2} flexWrap={'wrap'} gap={1}>
|
||||
<Chip label={`Orders-${datestatus}`} color="primary" variant="light" />
|
||||
<Chip
|
||||
label={
|
||||
<Typography noWrap color="secondary">
|
||||
{dayjs(startdate).format('DD/MM/YYYY')} - {dayjs(enddate).format('DD/MM/YYYY')}
|
||||
</Typography>
|
||||
}
|
||||
variant="combined"
|
||||
color="warning"
|
||||
deleteIcon={<CalendarMonth style={{ fontSize: 18 }} />}
|
||||
onDelete={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
onClick={() => setOpen(true)}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
{(!startdate || !enddate) && (
|
||||
<>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Chip label="Orders-All" color="primary" variant="light" size="small" />
|
||||
{/* <Chip label={<Typography noWrap color="secondary">ALL</Typography>} variant="combined" color='warning' size='small' /> */}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
<Stack style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<LocationAutocomplete
|
||||
// ref={locationRef}
|
||||
locaName={locaName}
|
||||
setAppId={setAppId}
|
||||
setLocoName={setLocoName}
|
||||
// setPage={setPage}
|
||||
sx={{ minWidth: 250, maxWidth: 1000, flex: 1 }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
{/* table */}
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableCell># </TableCell>
|
||||
<TableCell>Rider </TableCell>
|
||||
<TableCell> Orders</TableCell>
|
||||
<TableCell> Pending</TableCell>
|
||||
{/* <TableCell> Assigned</TableCell>
|
||||
<TableCell>Accepted </TableCell>
|
||||
<TableCell>Arrived </TableCell>
|
||||
<TableCell>Picked </TableCell>
|
||||
<TableCell>Active </TableCell>
|
||||
<TableCell>Skipped </TableCell> */}
|
||||
<TableCell>Cancelled </TableCell>
|
||||
<TableCell>Delivered </TableCell>
|
||||
<TableCell align="center">KMS </TableCell>
|
||||
<TableCell align="center">Amount </TableCell>
|
||||
<TableCell align="center">Action </TableCell>
|
||||
</TableHead>
|
||||
{/* ============================================ || TableBody || ============================================ */}
|
||||
|
||||
{isLoadingReports && <OrdersTableSkeleton col={10} />}
|
||||
|
||||
<TableBody>
|
||||
{rows?.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={15}>
|
||||
<Stack width={'100%'} direction={'row'} justifyContent={'center'}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
{rows?.length != 0 &&
|
||||
rows?.map((row, index) => (
|
||||
<>
|
||||
{/* // ============================================ || tablerow 1 || ============================================ */}
|
||||
<TableRow
|
||||
key={row.tenantname}
|
||||
sx={{
|
||||
cursor: openRow === row.userid ? 'pointer' : null
|
||||
}}
|
||||
>
|
||||
<TableCell align="left">{index + 1}</TableCell>
|
||||
<TableCell>
|
||||
<Stack spacing={1}>
|
||||
<Typography sx={{ whiteSpace: 'noWrap' }}> {` ${row?.firstname} ${row?.lastname}`}</Typography>
|
||||
<Typography variant="subtitle2">Id : {row.userid} </Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{coloredCell(row.totalorders)}</TableCell>
|
||||
<TableCell>{coloredCell(row.pending)}</TableCell>
|
||||
{/* <TableCell >{coloredCell(row.assigned)}</TableCell>
|
||||
<TableCell >{coloredCell(row.accepted)}</TableCell>
|
||||
<TableCell >{coloredCell(row.arrived)}</TableCell>
|
||||
<TableCell >{coloredCell(row.picked)}</TableCell>
|
||||
<TableCell >{coloredCell(row.active)}</TableCell>
|
||||
<TableCell >{coloredCell(row.skipped)}</TableCell> */}
|
||||
<TableCell>{coloredCell(row.cancelled)}</TableCell>
|
||||
<TableCell>{coloredCell(row.delivered)}</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Stack direction="column" spacing={1}>
|
||||
<Tooltip title="Kms" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={`${Number(row.kms).toFixed(2)}`}
|
||||
color={row.kms ? 'error' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Actual Kms" placement="bottom">
|
||||
<Chip
|
||||
size="small"
|
||||
label={`${Number(row.cumulativekms).toFixed(2)}`}
|
||||
color={row.cumulativekms ? 'success' : 'secondary'}
|
||||
variant="combined"
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Tooltip title="Total Amount" placement="top">
|
||||
<Chip
|
||||
size="small"
|
||||
label={formatNumberToRupees(row.charges >= row.deliveryamt ? row.charges : row.deliveryamt)}
|
||||
variant="combined"
|
||||
color={row.deliveryamt ? 'primary' : 'secondary'}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
minWidth: 100
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Stack display={'flex'} flexDirection={'row'} gap={1} alignItems={'center'}>
|
||||
<IconButton
|
||||
aria-label="expand row"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const isOpening = openRow !== row.userid;
|
||||
|
||||
// toggle row
|
||||
setOpenRow(isOpening ? row.userid : null);
|
||||
|
||||
// ❌ closing → no API
|
||||
if (!isOpening) return;
|
||||
|
||||
// ✅ opening → call API
|
||||
fetchTenantSummary(row.userid);
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: openRow === row.userid ? 'primary.main' : null,
|
||||
color: openRow === row.userid ? 'white' : null,
|
||||
'&:hover': {
|
||||
bgcolor: openRow === row.userid ? 'primary.main' : '#e1bee7'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{openRow === row.userid ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
|
||||
</IconButton>
|
||||
<IconButton>
|
||||
<IoLocationOutline
|
||||
onClick={() => {
|
||||
setMapOpen(true);
|
||||
getuserdeliverylogs(row?.userid);
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/* // ============================================ || collapsive row || ============================================ */}
|
||||
{openRow === row.userid && (
|
||||
<TableRow
|
||||
sx={{
|
||||
cursor: openRow === row.userid ? 'pointer' : null
|
||||
}}
|
||||
>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={15}>
|
||||
<Collapse in={true} timeout="auto" unmountOnExit>
|
||||
<MainCard content={false} sx={{ margin: 1 }}>
|
||||
<Table size="small" aria-label="purchases">
|
||||
<TableHead sx={{ bgcolor: 'primary.lighter' }}>
|
||||
<TableRow>
|
||||
<TableCell>#</TableCell>
|
||||
<TableCell>Client</TableCell>
|
||||
<TableCell align="center">All</TableCell>
|
||||
<TableCell align="center">Pending</TableCell>
|
||||
<TableCell align="center">Completed</TableCell>
|
||||
<TableCell align="center">Cancelled</TableCell>
|
||||
<TableCell align="center">Kms</TableCell>
|
||||
<TableCell align="center">Amount</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading && <OrdersTableSkeleton col={3} />}
|
||||
{tenantData?.map((row, index) => (
|
||||
<TableRow key={row.tenantname}>
|
||||
<TableCell align="left">{index + 1}</TableCell>
|
||||
<TableCell align="left">
|
||||
<Stack direction="row" sx={{ ml: -2 }}>
|
||||
{row.tenantname}
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.totalorders)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriespending)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriescompleted)}</TableCell>
|
||||
<TableCell align="center">{coloredCell(row.deliveriescancelled)}</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Chip
|
||||
size="small"
|
||||
label={row.kms}
|
||||
color="error"
|
||||
variant="combined"
|
||||
sx={{
|
||||
mr: 1,
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
label={row.cumulativekms}
|
||||
color="success"
|
||||
variant="combined"
|
||||
sx={{
|
||||
minWidth: 80
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center">
|
||||
<Chip
|
||||
size="small"
|
||||
label={formatNumberToRupees(row.charges >= row.deliveryamt ? row.charges : row.deliveryamt)}
|
||||
sx={{
|
||||
color: 'primary.main',
|
||||
bgcolor: '#e1bee7',
|
||||
border: '1px solid #662582 ',
|
||||
minWidth: 100
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</MainCard>
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Divider />
|
||||
{rows?.length != 0 && (
|
||||
<Stack direction={'row'} sx={{ display: 'flex', justifyContent: 'end', px: 1, py: 2 }}>
|
||||
<Typography variant="h5">Total :</Typography>
|
||||
<Typography variant="h5" sx={{ ml: 5, mr: 1.5 }}>
|
||||
{formatNumberToRupees(total)}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</MainCard>
|
||||
<Dialog
|
||||
maxWidth={'xl'}
|
||||
fullScreen
|
||||
open={mapOpen}
|
||||
onClose={() => {
|
||||
setMapOpen(false);
|
||||
}}
|
||||
>
|
||||
<DialogContent>{logDetails && <RidersRoutes details={logDetails} />}</DialogContent>
|
||||
</Dialog>
|
||||
{/* ============================================= || filter Dialog | ============================================= */}
|
||||
<DateFilterDialog
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onSelect={(range) => {
|
||||
setStartdate(range.startDate);
|
||||
setEnddate(range.endDate);
|
||||
setDatestatus(range.label);
|
||||
console.log('Selected Date Range:', range);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
1942
src/pages/nearle/requests/requests.js
Normal file
1942
src/pages/nearle/requests/requests.js
Normal file
File diff suppressed because it is too large
Load Diff
452
src/pages/nearle/riders/createrider.js
Normal file
452
src/pages/nearle/riders/createrider.js
Normal file
@@ -0,0 +1,452 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
// material-ui
|
||||
|
||||
import { Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography } 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 { setLocationType } from 'react-geocode';
|
||||
|
||||
// const avatarImage = require.context('assets/images/users', true);
|
||||
|
||||
// styles & constant
|
||||
// const ITEM_HEIGHT = 48;
|
||||
// const ITEM_PADDING_TOP = 8;
|
||||
// const MenuProps = {
|
||||
// PaperProps: {
|
||||
// style: {
|
||||
// maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
const Createrider = () => {
|
||||
// const [role, setRole] = useState('');
|
||||
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 navigate = useNavigate();
|
||||
|
||||
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'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
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 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);
|
||||
});
|
||||
};
|
||||
|
||||
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 (!businessname) {
|
||||
// opentoast('Fill Business name')
|
||||
// } else if (!businessno) {
|
||||
// opentoast('Fill Registration No')
|
||||
// }
|
||||
// else
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <Loader />}
|
||||
|
||||
<Grid item xs={12} sx={{ mb: 2 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h3">Create Rider</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<MainCard>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<MainCard
|
||||
// title="Contact Information"
|
||||
sx={{ height: '100%' }}
|
||||
>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-last-name">Admin Name</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="personal-last-name"
|
||||
placeholder="Name"
|
||||
onChange={(e) => setFirstname(e.target.value)}
|
||||
value={firstname}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}></Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-phone">Phone Number</InputLabel>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<Select defaultValue="+1" disabled sx={{ cursor: 'not-allowed' }}>
|
||||
<MenuItem value="+1">+91</MenuItem>
|
||||
</Select>
|
||||
<TextField
|
||||
type="number"
|
||||
id="personal-phone"
|
||||
// format="##########"
|
||||
// mask="_"
|
||||
fullWidth
|
||||
// customInput={TextField}
|
||||
placeholder="Phone Number"
|
||||
// defaultValue="8654239581"
|
||||
// onBlur={() => { }}
|
||||
onChange={(e) => {
|
||||
if (e.target.value.toString().length <= 10) {
|
||||
setMobilenumber(e.target.value);
|
||||
}
|
||||
}}
|
||||
value={mobilenumber}
|
||||
autoComplete="off"
|
||||
// disabled
|
||||
sx={{ cursor: 'not-allowed' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Email Address</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
placeholder="Email Address"
|
||||
onChange={(e) => setEmailaddress(e.target.value)}
|
||||
value={emailaddress}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-address">Address</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
|
||||
id="personal-address"
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
inputRef={materialRef}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Suburb</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Location"
|
||||
onChange={(e) => setSuburb(e.target.value)}
|
||||
value={suburb}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">City</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="956754"
|
||||
// type='number'
|
||||
id="personal-zipcode"
|
||||
placeholder="City"
|
||||
onChange={(e) => setCity(e.target.value)}
|
||||
value={city}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">State</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="State"
|
||||
onChange={(e) => setState(e.target.value)}
|
||||
value={state}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">Post Code</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="956754"
|
||||
type="number"
|
||||
id="personal-zipcode"
|
||||
placeholder="Zipcode"
|
||||
onChange={(e) => setZipcode(e.target.value)}
|
||||
value={zipcode}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Door No</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Door No"
|
||||
onChange={(e) => setDoorno(e.target.value)}
|
||||
value={doorno}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Landmark</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
placeholder="Landmark"
|
||||
onChange={(e) => setLandmark(e.target.value)}
|
||||
value={landmark}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="flex-end" alignItems="center" spacing={2}>
|
||||
<Button variant="contained" onClick={() => createprofile()}>
|
||||
Create
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Createrider;
|
||||
1101
src/pages/nearle/riders/editRider.js
Normal file
1101
src/pages/nearle/riders/editRider.js
Normal file
File diff suppressed because it is too large
Load Diff
501
src/pages/nearle/riders/riders.js
Normal file
501
src/pages/nearle/riders/riders.js
Normal file
@@ -0,0 +1,501 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Geocode from 'react-geocode';
|
||||
import { Empty } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FaRegEdit } from 'react-icons/fa';
|
||||
import {
|
||||
Avatar,
|
||||
Stack,
|
||||
Chip,
|
||||
Typography,
|
||||
Table,
|
||||
TableCell,
|
||||
TableBody,
|
||||
TableHead,
|
||||
IconButton,
|
||||
Tabs,
|
||||
Tab,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
TableContainer,
|
||||
Divider,
|
||||
Backdrop,
|
||||
Collapse
|
||||
} from '@mui/material';
|
||||
var utc = require('dayjs/plugin/utc');
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
dayjs.extend(utc);
|
||||
import MainCard from 'components/MainCard';
|
||||
import TitleCard from 'components/nearle_components/TitleCard';
|
||||
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
|
||||
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
|
||||
import CircularLoader from 'components/CircularLoader';
|
||||
import { fetchAllRiders, getallridersummary, getriderstatus } from 'pages/api/api';
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
import LoaderWithImage from 'components/nearle_components/LoaderWithImage';
|
||||
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import axios from 'axios';
|
||||
import LocationOnIcon from '@mui/icons-material/LocationOn';
|
||||
import BatteryStdIcon from '@mui/icons-material/BatteryStd';
|
||||
import SpeedIcon from '@mui/icons-material/Speed';
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import PowerIcon from '@mui/icons-material/Power';
|
||||
import GpsFixedIcon from '@mui/icons-material/GpsFixed';
|
||||
|
||||
const Riders = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const loadMoreRef = useRef();
|
||||
const containerRef = useRef();
|
||||
const [searchword, setSearchword] = useState('');
|
||||
const [debouncedSearch, setDebouncedSearch] = useState('');
|
||||
const [locaName, setLocoName] = useState('All');
|
||||
const [appId, setAppId] = useState(0);
|
||||
const [tabvalue, setTabvalue] = useState(0);
|
||||
const roleid = localStorage.getItem('roleid');
|
||||
const [logsRow, setLogsRow] = useState(null);
|
||||
const [riderLogsdata, setRiderLogsdata] = useState(null);
|
||||
|
||||
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
|
||||
|
||||
|
||||
|
||||
const handleChangetab = (e, i) => {
|
||||
setTabvalue(i);
|
||||
setLogsRow(null);
|
||||
};
|
||||
|
||||
// ==============================|| getallridersummary||============================== //
|
||||
|
||||
const { data: allRidersSummary, isLoading: riderSummarysLoading } = useQuery({
|
||||
queryKey: ['allriders', appId, tabvalue],
|
||||
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
|
||||
});
|
||||
useEffect(() => {
|
||||
if (ridersStatus) {
|
||||
console.log('Success:', ridersStatus);
|
||||
}
|
||||
}, [ridersStatus]);
|
||||
// ==============================|| fetchAllRiders||============================== //
|
||||
const {
|
||||
data: allRidersData,
|
||||
isLoading: allRidersLoading,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
hasNextPage
|
||||
} = useInfiniteQuery({
|
||||
queryKey: ['allriders', appId, debouncedSearch, tabvalue],
|
||||
queryFn: fetchAllRiders,
|
||||
getNextPageParam: (lastPage, pages) => (lastPage.details?.length ? pages.length + 1 : undefined)
|
||||
});
|
||||
|
||||
const rows = allRidersData?.pages.flatMap((page) => page.details) || [];
|
||||
useEffect(() => {
|
||||
if (!hasNextPage) return;
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
fetchNextPage();
|
||||
}
|
||||
},
|
||||
{
|
||||
root: document.querySelector('.MuiTableContainer-root'), // 👈 or explicitly TableContainer
|
||||
rootMargin: '0px',
|
||||
threshold: 1.0
|
||||
}
|
||||
);
|
||||
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
|
||||
return () => {
|
||||
if (loadMoreRef.current) observer.unobserve(loadMoreRef.current);
|
||||
};
|
||||
}, [hasNextPage, fetchNextPage]);
|
||||
|
||||
const handleScroll = (event) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
|
||||
if (scrollTop + clientHeight >= scrollHeight - 50) {
|
||||
if (hasNextPage && !isFetchingNextPage) {
|
||||
fetchNextPage();
|
||||
}
|
||||
}
|
||||
};
|
||||
const errMessage = riderstatusIsError ? riderStatusError : null;
|
||||
useEffect(() => {
|
||||
if (errMessage) {
|
||||
OpenToast(errMessage, 'error', 2000);
|
||||
}
|
||||
}, [errMessage]);
|
||||
return (
|
||||
<>
|
||||
{
|
||||
<Backdrop
|
||||
sx={{
|
||||
color: '#fff',
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1
|
||||
}}
|
||||
open={allRidersLoading || riderSummarysLoading || riderStatusLoading} // when loader = true, backdrop covers the page
|
||||
>
|
||||
<CircularLoader color="inherit" />
|
||||
</Backdrop>
|
||||
}
|
||||
{/* ============================================= || titlecard | ============================================= */}
|
||||
|
||||
<TitleCard title="Riders">
|
||||
<LocationAutocomplete
|
||||
locaName={locaName}
|
||||
setAppId={setAppId}
|
||||
setLocoName={setLocoName}
|
||||
sx={{ width: { xs: '100%', custom450: 300 }, zIndex: '100' }}
|
||||
/>
|
||||
</TitleCard>
|
||||
<Stack
|
||||
minWidth={'100%'}
|
||||
flexDirection="row"
|
||||
sx={{
|
||||
p: 1.5,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap-reverse',
|
||||
gap: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'bg.main',
|
||||
mt: 2
|
||||
}}
|
||||
>
|
||||
<Tabs value={tabvalue} onChange={handleChangetab} variant="scrollable" scrollButtons="auto">
|
||||
<Tab
|
||||
label="ALL"
|
||||
iconPosition="end"
|
||||
icon={searchword ? null : <Chip label={allRidersSummary?.total} color="primary" variant="light" size="small" />}
|
||||
/>
|
||||
<Tab
|
||||
label="Active"
|
||||
iconPosition="end"
|
||||
icon={searchword ? null : <Chip label={allRidersSummary?.active} color="primary" variant="light" size="small" />}
|
||||
/>
|
||||
</Tabs>
|
||||
<DebounceSearchBar
|
||||
value={searchword}
|
||||
onChange={setSearchword}
|
||||
onDebouncedChange={setDebouncedSearch}
|
||||
sx={{ width: { xs: '100%', custom600: 275 }, m: 0 }}
|
||||
/>
|
||||
</Stack>
|
||||
<MainCard content={false}>
|
||||
<TableContainer
|
||||
ref={containerRef}
|
||||
onScroll={handleScroll}
|
||||
sx={{
|
||||
maxHeight: 'calc(100vh - 185px)',
|
||||
overflow: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '12px', // scroll bar width
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: theme.palette.primary.main, // thumb color
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
backgroundColor: theme.palette.primary.dark, // hover color
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
backgroundColor: theme.palette.primary.lighter,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Table stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ position: 'sticky !important' }}>S.NO</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important', whiteSpace: 'nowrap', minWidth: 100 }}>User ID</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}> Rider </TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}> Address </TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}> Vehicle </TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}> Shift </TableCell>
|
||||
<TableCell align="center" sx={{ position: 'sticky !important' }}>
|
||||
Time
|
||||
</TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}> Fare </TableCell>
|
||||
<TableCell sx={{ position: 'sticky !important' }}> Fuel </TableCell>
|
||||
<TableCell align="center" sx={{ position: 'sticky !important' }}>
|
||||
Status
|
||||
</TableCell>
|
||||
{roleid == 1 && (
|
||||
<TableCell align="center" sx={{ position: 'sticky !important' }}>
|
||||
Action{' '}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<Divider />
|
||||
<TableBody>
|
||||
{allRidersLoading && <OrdersTableSkeleton col={6} />}
|
||||
{rows?.length == 0 && !allRidersLoading && (
|
||||
<>
|
||||
<TableCell colSpan={11}>
|
||||
<Stack width={'100%'} direction={'row'} justifyContent={'center'}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
{rows?.length != 0 &&
|
||||
rows?.map((row, index) => {
|
||||
return (
|
||||
<>
|
||||
<TableRow key={index + 1} sx={{ cursor: 'pointer' }}>
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
<TableCell>
|
||||
<Chip label={row?.userid} color="primary" variant="light" />
|
||||
</TableCell>
|
||||
<TableCell align="left" sx={{ paddingLeft: '0px !important' }}>
|
||||
<Stack direction="row" alignItems="center" gap={2} justifyContent="flex-start">
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 35,
|
||||
height: 35,
|
||||
fontSize: 20
|
||||
}}
|
||||
>
|
||||
{row.fullname?.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Stack direction="column" gap={1}>
|
||||
<Typography>{`${row.username}`}</Typography>
|
||||
<Typography variant="caption">{row.contactno}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<Tooltip title={row.address}>
|
||||
<Stack direction="column">
|
||||
<Typography variant="caption">{row.suburb || row.address.slice(0, 20)}</Typography>
|
||||
<Typography>{row.city}</Typography>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell align="left">{row.vehicleno}</TableCell>
|
||||
<TableCell>{row.shiftid}</TableCell>
|
||||
<TableCell align="center">
|
||||
<Stack display={'flex'} flexDirection={'column'} gap={1}>
|
||||
<Chip
|
||||
size="small"
|
||||
color="success"
|
||||
variant="light"
|
||||
label={dayjs(`${dayjs().format('MM-DD-YYYY')} ${row.starttime}`).format('hh:mm A')}
|
||||
sx={{ width: 100 }}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
color="error"
|
||||
variant="light"
|
||||
label={dayjs(`${dayjs().format('MM-DD-YYYY')} ${row.endtime}`).format('hh:mm A')}
|
||||
sx={{ width: 100 }}
|
||||
/>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="left">{row.basefare}</TableCell>
|
||||
<TableCell align="left">{row.fuelcharge}</TableCell>
|
||||
{tabvalue == 0 ? (
|
||||
<TableCell>
|
||||
{row.status == 'Active' && (
|
||||
<Chip label="Active" color="success" size="small" sx={{ width: 80 }} variant="light" />
|
||||
)}
|
||||
{row.status == 'InActive' && (
|
||||
<Chip label="In Active" color="error" size="small" sx={{ width: 80 }} variant="light" />
|
||||
)}
|
||||
</TableCell>
|
||||
) : (
|
||||
<TableCell>
|
||||
{(() => {
|
||||
const state = ridersStatus?.find((status) => status.userid === row.userid);
|
||||
const statusText = state?.status;
|
||||
console.log('statusText', state);
|
||||
return (
|
||||
<Chip
|
||||
label={statusText || 'Unknown'}
|
||||
color={
|
||||
statusText === 'Active'
|
||||
? 'primary'
|
||||
: statusText === 'Offline'
|
||||
? 'error'
|
||||
: statusText === 'Online'
|
||||
? 'success'
|
||||
: 'default'
|
||||
}
|
||||
size="small"
|
||||
sx={{ width: 80 }}
|
||||
variant="light"
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
{roleid == 1 && (
|
||||
<TableCell align="center">
|
||||
<Stack direction={'row'}>
|
||||
<Tooltip title="Edit Rider">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
navigate('/nearle/riders/edit', { state: { riderdata: row } });
|
||||
}}
|
||||
>
|
||||
<FaRegEdit />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{tabvalue != 0 && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
if (row.userid == logsRow) {
|
||||
setLogsRow(null);
|
||||
} else {
|
||||
setLogsRow(row.userid);
|
||||
getRiderLogs(row.userid);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{row.userid == logsRow ? <UpOutlined /> : <DownOutlined />}
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
{logsRow === row.userid && tabvalue !== 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={11} sx={{ p: 0 }}>
|
||||
<Collapse in={logsRow === row.userid} timeout="auto" unmountOnExit>
|
||||
<MainCard content={false}>
|
||||
<Table size="small">
|
||||
{/* Header */}
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Location</TableCell>
|
||||
<TableCell>Battery</TableCell>
|
||||
<TableCell>Charging</TableCell>
|
||||
<TableCell>Speed</TableCell>
|
||||
<TableCell>Accuracy</TableCell>
|
||||
<TableCell>Time</TableCell>
|
||||
<TableCell>Order</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
{/* Body */}
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<LocationOnIcon color="error" fontSize="small" />
|
||||
<Typography variant="body2">
|
||||
{riderLogsdata?.latitude}, {riderLogsdata?.longitude}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<BatteryStdIcon fontSize="small" />
|
||||
<Typography>{riderLogsdata?.battery || 'N/A'}</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<PowerIcon color={riderLogsdata?.is_charging ? 'success' : 'disabled'} fontSize="small" />
|
||||
<Typography>{riderLogsdata?.is_charging ? 'Charging' : 'Not Charging'}</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<SpeedIcon fontSize="small" />
|
||||
<Typography>{riderLogsdata?.speed} km/h</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<GpsFixedIcon fontSize="small" />
|
||||
<Typography>{riderLogsdata?.accuracy} m</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<AccessTimeIcon fontSize="small" />
|
||||
<Typography>{riderLogsdata?.logdate}</Typography>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{riderLogsdata?.orderid || 'N/A'}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={riderLogsdata?.status || 'unknown'}
|
||||
color={riderLogsdata?.status === 'idle' ? 'warning' : 'success'}
|
||||
size="small"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</MainCard>
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
{rows?.length != 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={15} rowSpan={3}>
|
||||
<div ref={loadMoreRef} style={{ height: 40, textAlign: 'center' }}>
|
||||
{isFetchingNextPage ? <LoaderWithImage /> : hasNextPage ? <LoaderWithImage /> : 'No More Riders'}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</MainCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Riders;
|
||||
173
src/pages/nearle/viewProfile.js
Normal file
173
src/pages/nearle/viewProfile.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Grid, TextField, Typography } from '@mui/material';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import CircularLoader from 'components/CircularLoader';
|
||||
import Loader from 'components/Loader';
|
||||
import MainCard from 'components/MainCard';
|
||||
import { getusers } from 'pages/api/api';
|
||||
|
||||
const ViewProfile = () => {
|
||||
const {
|
||||
data: userData,
|
||||
isLoading
|
||||
} = useQuery({
|
||||
queryKey: ['getuser'],
|
||||
queryFn: getusers
|
||||
});
|
||||
return (
|
||||
<>
|
||||
{isLoading && (
|
||||
<>
|
||||
<Loader />
|
||||
<CircularLoader />
|
||||
</>
|
||||
)}
|
||||
|
||||
<MainCard
|
||||
title={
|
||||
<Typography variant="h3" sx={{ m: 3 }}>
|
||||
User Profile
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Grid container spacing={4}>
|
||||
{/* ==============================|| userid ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'User ID'}
|
||||
value={userData?.userid || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
{/* ==============================|| Name ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'User Name'}
|
||||
value={userData?.fullname || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
{/* ==============================|| Location ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'App Location '}
|
||||
value={userData?.applocation || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* ==============================|| Authname ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'Auth Name'}
|
||||
value={userData?.authname || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{/* ==============================|| contactno ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'Contact No'}
|
||||
value={userData?.contactno || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
{/* ==============================|| email ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'E-Mail'}
|
||||
value={userData?.email || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
{/* ==============================|| address ||============================== */}
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'Address'}
|
||||
value={userData?.address || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
{/* ==============================|| Location ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'Location'}
|
||||
value={userData?.suburb || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
{/* ==============================|| city ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'City'}
|
||||
value={userData?.city || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
{/* ==============================|| state ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'State'}
|
||||
value={userData?.state || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
{/* ==============================|| Postcode ||============================== */}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={'Postcode'}
|
||||
value={userData?.postcode || ''}
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewProfile;
|
||||
31
src/pages/titleCard.js
Normal file
31
src/pages/titleCard.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { CardActions, Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
const TitleCard = ({ title, secondary, sx }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<CardActions
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
top: '30px',
|
||||
bgcolor: theme.palette.background.default,
|
||||
zIndex: 10,
|
||||
width: '100%',
|
||||
...sx
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" gap={2} sx={{ flexWrap: 'wrap' }}>
|
||||
<Typography variant="h3">{title}</Typography>
|
||||
{secondary}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardActions>
|
||||
);
|
||||
};
|
||||
|
||||
export default TitleCard;
|
||||
Reference in New Issue
Block a user