219 lines
10 KiB
JavaScript
219 lines
10 KiB
JavaScript
import { useState, useMemo, Fragment } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import {
|
|
Grid, Card, Stack, Button, TextField, MenuItem, InputAdornment, Box, Tabs, Tab,
|
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, IconButton, Tooltip,
|
|
TablePagination, Typography, Chip, Collapse
|
|
} from '@mui/material';
|
|
import SearchIcon from '@mui/icons-material/Search';
|
|
import AddIcon from '@mui/icons-material/Add';
|
|
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
|
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
|
|
import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined';
|
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
|
import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined';
|
|
import PowerSettingsNewOutlinedIcon from '@mui/icons-material/PowerSettingsNewOutlined';
|
|
|
|
import PageHeader from '@/components/PageHeader';
|
|
import StatCard from '@/components/StatCard';
|
|
import StatusChip from '@/components/StatusChip';
|
|
import UserAvatar from '@/components/UserAvatar';
|
|
import TabLabelCount from '@/components/TabLabelCount';
|
|
import { riders, riderLogs } from '@/data/mock';
|
|
import { useFilters } from '@/store/Filters';
|
|
import { inr } from '@/utils/format';
|
|
|
|
const TABS = [
|
|
{ key: 'all', label: 'ALL' },
|
|
{ key: 'active', label: 'Active' }
|
|
];
|
|
|
|
export default function Riders() {
|
|
const navigate = useNavigate();
|
|
const [tab, setTab] = useState(0);
|
|
const [search, setSearch] = useState('');
|
|
const { location } = useFilters(); // global location — single source of truth
|
|
const [page, setPage] = useState(0);
|
|
const [rpp, setRpp] = useState(5);
|
|
const [expanded, setExpanded] = useState(null);
|
|
|
|
const tabKey = TABS[tab].key;
|
|
const filtered = useMemo(
|
|
() =>
|
|
riders.filter((r) => {
|
|
const matchTab = tabKey === 'all' ? true : r.status !== 'offline';
|
|
const matchLocation = location === 'all' || r.address.includes(location);
|
|
const matchSearch =
|
|
!search ||
|
|
[r.id, r.userId, r.name, r.phone, r.address, r.vehicle, r.vehicleNo]
|
|
.join(' ')
|
|
.toLowerCase()
|
|
.includes(search.toLowerCase());
|
|
return matchTab && matchLocation && matchSearch;
|
|
}),
|
|
[tabKey, location, search]
|
|
);
|
|
|
|
const paged = filtered.slice(page * rpp, page * rpp + rpp);
|
|
|
|
const counts = {
|
|
total: riders.length,
|
|
active: riders.filter((r) => r.status === 'online').length,
|
|
onDelivery: riders.filter((r) => r.status === 'on-delivery').length,
|
|
offline: riders.filter((r) => r.status === 'offline').length,
|
|
all: riders.length,
|
|
activeTab: riders.filter((r) => r.status !== 'offline').length
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<PageHeader
|
|
title="Riders"
|
|
breadcrumbs={[{ label: 'Riders' }]}
|
|
action={
|
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5} alignItems={{ sm: 'center' }}>
|
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => navigate('/riders/create')}>
|
|
Add Rider
|
|
</Button>
|
|
</Stack>
|
|
}
|
|
/>
|
|
|
|
<Grid container spacing={2.5} sx={{ mb: 1 }}>
|
|
<Grid item xs={12} sm={6} lg={3}><StatCard title="Total Riders" value={counts.total} icon={GroupsOutlinedIcon} caption="All registered" /></Grid>
|
|
<Grid item xs={12} sm={6} lg={3}><StatCard title="Active" value={counts.active} icon={CheckCircleOutlineIcon} color="success" caption="Online now" /></Grid>
|
|
<Grid item xs={12} sm={6} lg={3}><StatCard title="On Delivery" value={counts.onDelivery} icon={TwoWheelerOutlinedIcon} color="info" caption="In transit" /></Grid>
|
|
<Grid item xs={12} sm={6} lg={3}><StatCard title="Offline" value={counts.offline} icon={PowerSettingsNewOutlinedIcon} color="error" caption="Not available" /></Grid>
|
|
</Grid>
|
|
|
|
<Card sx={{ mt: 1.5 }}>
|
|
<Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ p: 2 }} alignItems={{ md: 'center' }}>
|
|
<TextField
|
|
size="small" placeholder="Search riders…" value={search} onChange={(e) => { setSearch(e.target.value); setPage(0); }}
|
|
sx={{ minWidth: 240 }}
|
|
InputProps={{ startAdornment: <InputAdornment position="start"><SearchIcon fontSize="small" /></InputAdornment> }}
|
|
/>
|
|
<Box sx={{ flexGrow: 1 }} />
|
|
</Stack>
|
|
|
|
<Box sx={{ px: 2, borderBottom: 1, borderColor: 'divider' }}>
|
|
<Tabs value={tab} onChange={(_, v) => { setTab(v); setPage(0); }}>
|
|
{TABS.map((t, i) => (
|
|
<Tab
|
|
key={t.key}
|
|
label={<TabLabelCount label={t.label} count={t.key === 'all' ? counts.all : counts.activeTab} active={tab === i} />}
|
|
/>
|
|
))}
|
|
</Tabs>
|
|
</Box>
|
|
|
|
<TableContainer>
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>S.NO</TableCell>
|
|
<TableCell>User ID</TableCell>
|
|
<TableCell>Rider</TableCell>
|
|
<TableCell>Address</TableCell>
|
|
<TableCell>Vehicle</TableCell>
|
|
<TableCell>Shift</TableCell>
|
|
<TableCell>Time</TableCell>
|
|
<TableCell align="right">Fare</TableCell>
|
|
<TableCell align="right">Fuel</TableCell>
|
|
<TableCell>Status</TableCell>
|
|
<TableCell align="center">Action</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{paged.map((r, i) => (
|
|
<Fragment key={r.id}>
|
|
<TableRow hover>
|
|
<TableCell>{page * rpp + i + 1}</TableCell>
|
|
<TableCell sx={{ fontWeight: 600, color: 'primary.main' }}>{r.userId}</TableCell>
|
|
<TableCell>
|
|
<Stack direction="row" spacing={1.25} alignItems="center">
|
|
<UserAvatar name={r.name} size={32} />
|
|
<Box>
|
|
<Typography variant="body2" sx={{ fontWeight: 600 }}>{r.name}</Typography>
|
|
<Typography variant="caption" color="text.secondary">{r.phone}</Typography>
|
|
</Box>
|
|
</Stack>
|
|
</TableCell>
|
|
<TableCell>{r.address}</TableCell>
|
|
<TableCell>
|
|
<Typography variant="body2">{r.vehicle}</Typography>
|
|
<Typography variant="caption" color="text.secondary">{r.vehicleNo}</Typography>
|
|
</TableCell>
|
|
<TableCell>{r.shift}</TableCell>
|
|
<TableCell>
|
|
<Stack direction="row" spacing={0.75} alignItems="center">
|
|
<Chip size="small" label={r.start} sx={{ bgcolor: 'success.lighter', color: 'success.main', fontWeight: 600 }} />
|
|
<Chip size="small" label={r.end} sx={{ bgcolor: 'error.lighter', color: 'error.main', fontWeight: 600 }} />
|
|
</Stack>
|
|
</TableCell>
|
|
<TableCell align="right" sx={{ fontWeight: 600 }}>{inr(r.fare)}</TableCell>
|
|
<TableCell align="right">{inr(r.fuel)}</TableCell>
|
|
<TableCell><StatusChip status={r.status} /></TableCell>
|
|
<TableCell align="center">
|
|
<Tooltip title="Edit">
|
|
<IconButton size="small" onClick={() => navigate(`/riders/${r.id}/edit`)}><EditOutlinedIcon fontSize="small" /></IconButton>
|
|
</Tooltip>
|
|
<Tooltip title={expanded === r.id ? 'Hide logs' : 'View logs'}>
|
|
<IconButton size="small" onClick={() => setExpanded(expanded === r.id ? null : r.id)}>
|
|
{expanded === r.id ? <KeyboardArrowUpIcon fontSize="small" /> : <KeyboardArrowDownIcon fontSize="small" />}
|
|
</IconButton>
|
|
</Tooltip>
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableCell colSpan={11} sx={{ p: 0, border: 0 }}>
|
|
<Collapse in={expanded === r.id} timeout="auto" unmountOnExit>
|
|
<Box sx={{ p: 2.5, bgcolor: 'grey.50' }}>
|
|
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 1.5 }}>Rider Logs</Typography>
|
|
<Table size="small">
|
|
<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>
|
|
<TableBody>
|
|
{riderLogs.map((log, li) => (
|
|
<TableRow key={li}>
|
|
<TableCell>{log.location}</TableCell>
|
|
<TableCell>{log.battery}</TableCell>
|
|
<TableCell>{log.charging}</TableCell>
|
|
<TableCell>{log.speed}</TableCell>
|
|
<TableCell>{log.accuracy}</TableCell>
|
|
<TableCell>{log.time}</TableCell>
|
|
<TableCell sx={{ fontWeight: 600, color: 'primary.main' }}>{log.order}</TableCell>
|
|
<TableCell><StatusChip status={log.status} /></TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</Box>
|
|
</Collapse>
|
|
</TableCell>
|
|
</TableRow>
|
|
</Fragment>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
<TablePagination
|
|
component="div" count={filtered.length} page={page} onPageChange={(_, p) => setPage(p)}
|
|
rowsPerPage={rpp} onRowsPerPageChange={(e) => { setRpp(+e.target.value); setPage(0); }} rowsPerPageOptions={[5, 10, 25]}
|
|
/>
|
|
</Card>
|
|
</>
|
|
);
|
|
}
|