initial commit

This commit is contained in:
2026-05-13 17:48:36 +05:30
commit 5a80256856
305 changed files with 80994 additions and 0 deletions

View 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;

View 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>
);
}

View 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>
);
}

View 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;

File diff suppressed because it is too large Load Diff

View 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);
}}
/>
</>
);
}

View 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;

View 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);
}}
/>
</>
);
}