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