updated the ui for the mobile view

This commit is contained in:
2026-06-09 13:12:18 +05:30
parent 8513c7c2ec
commit f18b680247
29 changed files with 3664 additions and 225 deletions

View File

@@ -30,8 +30,10 @@ import {
TextField,
Tooltip,
Typography,
Autocomplete
Autocomplete,
useMediaQuery
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import {
MdAssignment,
MdMyLocation,
@@ -71,6 +73,7 @@ import LocationAutocomplete from 'components/nearle_components/LocationAutocompl
import dayjs from 'dayjs';
import { OpenToast } from 'components/third-party/OpenToast';
import TableLoader from 'components/nearle_components/TableLoader';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
var utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
@@ -418,6 +421,8 @@ function kalmanSmoothGps(pings, options = {}) {
}
export default function OrdersDetails() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const loadMoreRef = useRef();
const containerRef = useRef();
const locationRef = useRef(null);
@@ -1313,6 +1318,267 @@ export default function OrdersDetails() {
'&::-webkit-scrollbar-track': { backgroundColor: DT.surfaceAlt }
}}
>
{isMobile ? (
/* ===================== MOBILE: card list ===================== */
<MobileCardList sx={{ p: 1.25 }}>
{fetchDeliveriesIsLoading ? (
<Stack alignItems="center" sx={{ py: 6 }}>
<LoaderWithImage />
<Typography variant="caption" color="text.secondary" sx={{ mt: 1 }}>
Loading orders
</Typography>
</Stack>
) : rows?.length == 0 ? (
<Stack alignItems="center" spacing={1.5} sx={{ py: 6 }}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdAssignment size={28} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
No orders to show
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary, textAlign: 'center', px: 2 }}>
{searchword ? 'Try a different keyword.' : 'Adjust the filters above to load orders.'}
</Typography>
</Stack>
) : (
rows?.map((row, index) => {
const statusKey = String(row.orderstatus || '').toLowerCase();
const rowStatusMeta = STATUS_META[statusKey] || {
label: row.orderstatus || '—',
color: BRAND,
icon: MdAssignment
};
const StatusIcon = rowStatusMeta.icon;
const cancelled = statusKey === 'cancelled';
const isDelivered = row.orderstatus === 'delivered';
return (
<MobileCard
key={row.deliveryid || `${row.orderid}-${index}`}
accent={rowStatusMeta.color}
header={
<Stack spacing={1.25}>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ minWidth: 0 }}>
<Typography variant="caption" sx={{ fontWeight: 700, color: DT.textMuted }}>
{String(page * rowsPerPage + index + 1).padStart(2, '0')}
</Typography>
<AccentAvatar color={BRAND} size={32}>
<MdGroups size={16} />
</AccentAvatar>
<Stack spacing={0.25} sx={{ minWidth: 0 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
{row.tenantname}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 700 }}>
#{row.orderid}
</Typography>
</Stack>
</Stack>
<Tooltip
title={isDelivered ? 'View rider route' : 'Available for delivered orders'}
placement="top"
>
<span>
<IconButton
size="small"
disabled={!isDelivered}
onClick={() => {
if (isDelivered) {
getdeliverylogs(row.deliveryid);
setMapTenant(row);
}
}}
sx={{
flexShrink: 0,
bgcolor: isDelivered ? soft(BRAND) : soft('#94a3b8'),
color: isDelivered ? BRAND : DT.textMuted,
border: `1px solid ${isDelivered ? edge(BRAND) : edge('#94a3b8')}`,
'&:hover': {
bgcolor: isDelivered ? BRAND : soft('#94a3b8'),
color: isDelivered ? '#fff' : DT.textMuted
}
}}
>
<MdMap size={14} />
</IconButton>
</span>
</Tooltip>
</Stack>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ flexWrap: 'wrap', gap: 0.5 }}>
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint(rowStatusMeta.color),
border: `1px solid ${edge(rowStatusMeta.color)}`,
color: rowStatusMeta.color,
fontSize: 11,
fontWeight: 800
}}
>
<StatusIcon size={12} /> {rowStatusMeta.label}
</Box>
{row.ridername && (
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: tint('#8b5cf6'),
border: `1px solid ${edge('#8b5cf6')}`,
color: '#8b5cf6',
fontSize: 11,
fontWeight: 800
}}
>
<MdDirectionsBike size={12} /> {row.ridername}
</Box>
)}
<Typography variant="caption" sx={{ color: DT.textMuted, fontWeight: 600 }}>
{dayjs(row.deliverydate).utc().format('DD/MM/YYYY · hh:mm A')}
</Typography>
</Stack>
</Stack>
}
>
<MobileFieldGrid>
<MobileField label="Pickup">
<Stack spacing={0.25} sx={{ minWidth: 0 }}>
<Typography sx={{ fontSize: 13, fontWeight: 700, color: DT.textPrimary }} noWrap>
{row.pickupcustomer || '—'}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }} noWrap>
{row.pickupcontactno}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }} noWrap>
{row.pickupsuburb || (row.Pickupaddress ? row.Pickupaddress.slice(0, 22) + '…' : '')}
</Typography>
{row.applocation && (
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 0.75,
py: 0.25,
borderRadius: 999,
bgcolor: tint('#10b981'),
border: `1px solid ${edge('#10b981')}`,
color: '#10b981',
fontSize: 10,
fontWeight: 800,
width: 'fit-content'
}}
>
<MdPlace size={11} /> {row.applocation}
</Box>
)}
</Stack>
</MobileField>
<MobileField label="Drop">
<Stack spacing={0.25} sx={{ minWidth: 0 }}>
<Typography sx={{ fontSize: 13, fontWeight: 700, color: DT.textPrimary }} noWrap>
{row.deliverycustomer || '—'}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }} noWrap>
{row.deliverycontactno}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }} noWrap>
{row.deliverysuburb || (row.deliveryaddress ? row.deliveryaddress.slice(0, 22) + '…' : '')}
</Typography>
</Stack>
</MobileField>
<MobileField label="Assigned">
<StampCell value={row.assigntime} formatDate={formatDate} formatTime={formatTime} />
</MobileField>
<MobileField label="Accepted">
<StampCell value={row.acceptedtime} formatDate={formatDate} formatTime={formatTime} />
</MobileField>
<MobileField label="Arrived">
<StampCell value={row.arrivaltime} formatDate={formatDate} formatTime={formatTime} />
</MobileField>
<MobileField label="Picked">
<StampCell
value={row.pickuptime}
formatDate={formatDate}
formatTime={formatTime}
success={row.deliverystatus === 'active'}
/>
</MobileField>
<MobileField label="Active">
<StampCell value={row.starttime} formatDate={formatDate} formatTime={formatTime} />
</MobileField>
<MobileField label="Delivered">
<StampCell value={row.deliverytime} formatDate={formatDate} formatTime={formatTime} />
</MobileField>
<MobileField label="Cancelled">
<StampCell value={row.canceltime} formatDate={formatDate} formatTime={formatTime} />
</MobileField>
<MobileField label="KMS · plan / act / rider" full>
<Stack direction="row" spacing={0.5} sx={{ flexWrap: 'wrap', gap: 0.5 }}>
<MetricPill
color="#ef4444"
icon={<MdStraighten size={11} />}
label={cancelled || row.kms == '' ? '0 km' : `${row.kms} km`}
tooltip="KMS"
/>
<MetricPill
color="#10b981"
icon={<MdStraighten size={11} />}
label={`${row.cumulativekms ?? 0} km`}
tooltip="Actual KMS"
/>
<MetricPill
color="#0ea5e9"
icon={<MdStraighten size={11} />}
label={`${row.previouskms || (cancelled ? '0.00' : row.kms) || 0} km`}
tooltip="Rider KMS"
/>
</Stack>
</MobileField>
<MobileField label="Charges · chg / amt" full>
<Stack direction="row" spacing={0.5} sx={{ flexWrap: 'wrap', gap: 0.5 }}>
<MetricPill
color="#ef4444"
icon={<MdCurrencyRupee size={11} />}
label={cancelled || row.deliverycharges == '' ? `0.00` : `${row.deliverycharges}.00`}
tooltip="Delivery Charge"
/>
<MetricPill
color="#10b981"
icon={<MdCurrencyRupee size={11} />}
label={row.deliveryamt == '' ? `0.00` : `${row.deliveryamt}.00`}
tooltip="Delivery Amount"
/>
</Stack>
</MobileField>
{row.ordernotes && (
<MobileField label="Notes" full>
<Stack direction="row" spacing={0.5} alignItems="flex-start">
<MdNoteAlt size={12} color={DT.textMuted} style={{ marginTop: 2, flexShrink: 0 }} />
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
{row.ordernotes}
</Typography>
</Stack>
</MobileField>
)}
</MobileFieldGrid>
</MobileCard>
);
})
)}
</MobileCardList>
) : (
<Table stickyHeader sx={{ minWidth: 1600 }}>
<TableHead>
<TableRow
@@ -1659,6 +1925,7 @@ export default function OrdersDetails() {
)}
</TableBody>
</Table>
)}
<Divider />
{rows?.length !== 0 && (
<Stack justifyContent="center" alignItems="center" sx={{ width: '100%', py: 2 }}>
@@ -1682,7 +1949,8 @@ export default function OrdersDetails() {
onClose={() => setReportDialog(false)}
fullWidth
maxWidth="sm"
PaperProps={{ sx: { borderRadius: 2.5, overflow: 'hidden' } }}
fullScreen={isMobile}
PaperProps={{ sx: { borderRadius: { xs: 0, sm: 3 }, overflow: 'hidden' } }}
>
<DialogTitle
sx={{

View File

@@ -22,7 +22,9 @@ import {
TableRow,
TextField,
Tooltip,
Typography
Typography,
useMediaQuery,
useTheme
} from '@mui/material';
import {
MdAssignment,
@@ -52,6 +54,7 @@ import Loader from 'components/Loader';
import DateFilterDialog from 'components/DateFilterDialog';
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
import { OpenToast } from 'components/third-party/OpenToast';
@@ -198,6 +201,9 @@ function formatNumberToRupees(value) {
// Orders Summary
// ============================================================================
export default function OrdersReport() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const locationRef = useRef(null);
const tenantRef = useRef(null);
@@ -716,6 +722,251 @@ export default function OrdersReport() {
background: '#fff'
}}
>
{isMobile ? (
<>
{isLoadingReports && (
<MobileCardList>
{[0, 1, 2, 3].map((i) => (
<MobileCard key={i} accent={DT.borderSubtle}>
<Box sx={{ height: 96 }} />
</MobileCard>
))}
</MobileCardList>
)}
{!isLoadingReports && filteredRows.length === 0 && (
<Stack alignItems="center" spacing={1.5} sx={{ py: 6, px: 2 }}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdAssignment size={28} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
No {isLocationGroup ? 'locations' : 'tenants'} to show
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary, textAlign: 'center', maxWidth: 360 }}>
{debouncedSearch
? 'Try a different keyword.'
: isErrorReports
? 'Something went wrong fetching the summary. Try a different filter.'
: 'Pick a zone, tenant, or date range to load the summary.'}
</Typography>
</Stack>
)}
{!isLoadingReports && filteredRows.length > 0 && (
<MobileCardList>
{filteredRows.map((row, index) => {
const rowKey = isLocationGroup ? row.locationname : row.tenantname;
const isOpen = openRow === rowKey;
const amount = Math.max(Number(row.charges) || 0, Number(row.deliveryamt) || 0);
const rowAccent = isLocationGroup ? '#0ea5e9' : BRAND;
return (
<MobileCard
key={`${rowKey}-${index}`}
accent={rowAccent}
selected={isOpen}
header={
<Stack direction="row" alignItems="center" spacing={1}>
<AccentAvatar color={rowAccent} size={36}>
{isLocationGroup ? <MdLocationOn size={18} /> : <MdStore size={18} />}
</AccentAvatar>
<Stack sx={{ minWidth: 0, flex: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
{(isLocationGroup ? row.locationname : row.tenantname) || '—'}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
ID #{isLocationGroup ? row.locationid : row.tenantid}
</Typography>
</Stack>
<Tooltip title={isOpen ? 'Hide rider breakdown' : 'View rider breakdown'} placement="top">
<IconButton
size="small"
onClick={() => {
const isOpening = !isOpen;
setOpenRow(isOpening ? rowKey : null);
if (!isOpening) return;
setRidersdata([]);
if (isLocationGroup) {
getriderlocationsummary(row.locationid);
} else {
getuserreportsummary(row.tenantid);
}
}}
sx={{
flexShrink: 0,
bgcolor: isOpen ? BRAND : soft(BRAND),
color: isOpen ? '#fff' : BRAND,
border: `1px solid ${edge(BRAND)}`,
'&:hover': { bgcolor: BRAND, color: '#fff' }
}}
>
{isOpen ? <MdExpandLess size={16} /> : <MdExpandMore size={16} />}
</IconButton>
</Tooltip>
</Stack>
}
>
<MobileFieldGrid columns={3}>
<MobileField label="All" align="center">
<CountCell value={row.totalorders} color={BRAND} icon={<MdLocalShipping size={11} />} />
</MobileField>
<MobileField label="Ord Pending" align="center">
<CountCell value={row.Orderspending} color="#f59e0b" icon={<MdHourglassEmpty size={11} />} />
</MobileField>
<MobileField label="Ord Cancelled" align="center">
<CountCell value={row.orderscancelled} color="#ef4444" icon={<MdCancel size={11} />} />
</MobileField>
<MobileField label="Ord Completed" align="center">
<CountCell value={row.orderscompleted} color="#10b981" icon={<MdCheckCircle size={11} />} />
</MobileField>
<MobileField label="Del Pending" align="center">
<CountCell value={row.deliveriespending} color="#f59e0b" icon={<MdHourglassEmpty size={11} />} />
</MobileField>
<MobileField label="Del Cancelled" align="center">
<CountCell value={row.deliveriescancelled} color="#ef4444" icon={<MdCancel size={11} />} />
</MobileField>
<MobileField label="Del Completed" align="center">
<CountCell value={row.deliveriescompleted} color="#10b981" icon={<MdCheckCircle size={11} />} />
</MobileField>
<MobileField label="Collection" align="center">
<MetricPill
color="#f59e0b"
icon={<MdCurrencyRupee size={11} />}
label={Number(row.collectionamt || 0).toFixed(2)}
tooltip="Collection Amount"
minWidth={90}
/>
</MobileField>
<MobileField label="Amount" align="center">
<MetricPill
color={BRAND}
icon={<MdCurrencyRupee size={11} />}
label={formatNumberToRupees(amount).replace('₹', '').trim()}
tooltip="Total Amount"
minWidth={100}
/>
</MobileField>
<MobileField label="KMS" align="center">
<MetricPill
color="#ef4444"
icon={<MdStraighten size={11} />}
label={`${Number(row.kms || 0).toFixed(2)} km`}
tooltip="KMS"
/>
</MobileField>
<MobileField label="Actual KMS" align="center">
<MetricPill
color="#10b981"
icon={<MdStraighten size={11} />}
label={`${Number(row.cumulativekms || 0).toFixed(2)} km`}
tooltip="Actual KMS"
/>
</MobileField>
</MobileFieldGrid>
{/* Collapsible rider breakdown — mobile */}
<Collapse in={isOpen} timeout="auto" unmountOnExit>
<Box sx={{ mt: 1.5 }}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 1 }}>
<AccentAvatar color={BRAND} size={24} selected>
<MdGroups size={12} />
</AccentAvatar>
<Typography
variant="caption"
sx={{ fontWeight: 800, color: DT.textPrimary, letterSpacing: 0.6, textTransform: 'uppercase' }}
>
Rider Breakdown
</Typography>
</Stack>
{!loading && (!ridersdata || ridersdata.length === 0) ? (
<Typography variant="caption" sx={{ color: DT.textMuted, display: 'block', py: 1, textAlign: 'center' }}>
No rider activity for this row.
</Typography>
) : (
<Stack spacing={1}>
{ridersdata.map((sub, sidx) => {
const subAmount = Math.max(Number(sub.charges) || 0, Number(sub.deliveryamt) || 0);
return (
<MobileCard key={`${sub.userid}-${sidx}`} accent={BRAND}>
<Stack direction="row" alignItems="center" spacing={0.75}>
<AccentAvatar color={BRAND} size={28}>
<MdPerson size={14} />
</AccentAvatar>
<Stack sx={{ minWidth: 0 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
{`${sub.firstname || ''} ${sub.lastname || ''}`.trim() || '—'}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
{sub.ridercontact || `ID #${sub.userid}`}
</Typography>
</Stack>
</Stack>
<MobileFieldGrid columns={3}>
<MobileField label="Orders" align="center">
<CountCell value={sub.orderscreated} color={BRAND} icon={<MdLocalShipping size={10} />} />
</MobileField>
<MobileField label="Deliveries" align="center">
<CountCell value={sub.totalorders} color="#0ea5e9" icon={<MdLocalShipping size={10} />} />
</MobileField>
<MobileField label="Pending" align="center">
<CountCell value={sub.deliveriespending} color="#f59e0b" icon={<MdHourglassEmpty size={10} />} />
</MobileField>
<MobileField label="Cancelled" align="center">
<CountCell value={sub.deliveriescancelled} color="#ef4444" icon={<MdCancel size={10} />} />
</MobileField>
<MobileField label="Completed" align="center">
<CountCell value={sub.deliveriescompleted} color="#10b981" icon={<MdCheckCircle size={10} />} />
</MobileField>
<MobileField label="Collection" align="center">
<MetricPill
color="#f59e0b"
icon={<MdCurrencyRupee size={10} />}
label={Number(sub.collectionamt || 0).toFixed(2)}
tooltip="Collection"
minWidth={80}
/>
</MobileField>
<MobileField label="KMS" align="center">
<MetricPill
color="#ef4444"
icon={<MdStraighten size={10} />}
label={`${Number(sub.kms || 0).toFixed(2)} km`}
tooltip="KMS"
/>
</MobileField>
<MobileField label="Actual KMS" align="center">
<MetricPill
color="#10b981"
icon={<MdStraighten size={10} />}
label={`${Number(sub.cumulativekms || 0).toFixed(2)} km`}
tooltip="Actual KMS"
/>
</MobileField>
<MobileField label="Amount" align="center">
<MetricPill
color={BRAND}
icon={<MdCurrencyRupee size={10} />}
label={formatNumberToRupees(subAmount).replace('₹', '').trim()}
tooltip="Total Amount"
minWidth={100}
/>
</MobileField>
</MobileFieldGrid>
</MobileCard>
);
})}
</Stack>
)}
</Box>
</Collapse>
</MobileCard>
);
})}
</MobileCardList>
)}
</>
) : (
<TableContainer
sx={{
maxHeight: { xs: 'calc(100vh - 280px)', md: 'calc(100vh - 240px)' },
@@ -1162,6 +1413,7 @@ export default function OrdersReport() {
</TableBody>
</Table>
</TableContainer>
)}
{/* ============================================= || Total Bar || ============================================= */}
{filteredRows.length > 0 && (

View File

@@ -25,6 +25,7 @@ import { useQuery } from '@tanstack/react-query';
import { fetchRidersLogs } from 'pages/api/api';
import RiderLocationMap from './RiderLocationMap';
import MainCard from 'components/MainCard';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import dayjs from 'dayjs';
import error500 from 'assets/images/maintenance/Error500.png';
@@ -34,6 +35,7 @@ const drawerWidth = 350;
const RidersLogs = () => {
const theme = useTheme();
const isDesktop = useMediaQuery('(min-width:900px)');
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [open, setOpen] = useState(false);
const [selectedRiders, setSelectedRiders] = useState([]);
const [riderSearch, setRiderSearch] = useState('');
@@ -74,7 +76,8 @@ const RidersLogs = () => {
ModalProps={{ keepMounted: true }}
sx={{
'& .MuiDrawer-paper': {
width: drawerWidth,
width: isMobile ? '100vw' : drawerWidth,
maxWidth: isMobile ? '100vw' : drawerWidth,
position: 'absolute',
left: 0,
top: 0,
@@ -142,7 +145,8 @@ const RidersLogs = () => {
<Divider />
</Fragment>
))
: riders?.map((row) => {
: !isMobile &&
riders?.map((row) => {
return (
<Fragment key={row.userid}>
<ListItem
@@ -210,6 +214,61 @@ const RidersLogs = () => {
);
})}
</List>
{/* Mobile: rider rows rendered as app-style cards (same selection behaviour) */}
{isMobile && !ridersIsLoading && !riderIsFetching && (
<MobileCardList>
{riders?.map((row) => {
const isActive = row.status == 'active';
const isSelected = selectedRiders?.length === 1 && selectedRiders[0]?.userid === row?.userid;
return (
<MobileCard
key={row.userid}
accent={isActive ? '#10b981' : '#ef4444'}
selected={isSelected}
header={
<Stack direction="row" alignItems="flex-start" spacing={1}>
<Checkbox
sx={{
p: 0.5,
color: isActive ? 'green' : 'red',
'&.Mui-checked': { color: isActive ? 'green' : 'red' }
}}
checked={isSelected}
onChange={(e) => {
if (e.target.checked) {
setSelectedRiders([row]);
} else {
setSelectedRiders(riders);
}
}}
/>
<Box sx={{ minWidth: 0, flexGrow: 1 }}>
<Typography noWrap sx={{ fontWeight: 600 }}>
{row.username?.slice(0, 25) || ''}
{row.username?.length > 25 && '...'}
</Typography>
<Typography variant="caption" color="text.secondary" noWrap>
{row.contactno || '##########'}
</Typography>
</Box>
</Stack>
}
>
<MobileFieldGrid>
<MobileField label="User ID">
<Typography sx={{ fontSize: 13, fontWeight: 600, color: isActive ? 'success.main' : 'error.main' }} noWrap>
{row.userid}
</Typography>
</MobileField>
<MobileField label="Status" value={isActive ? 'Active' : 'Inactive'} />
<MobileField label="Last Log" value={dayjs(row.logdate).format('DD/MM/YYYY hh:mm A')} full />
</MobileFieldGrid>
</MobileCard>
);
})}
</MobileCardList>
)}
</Drawer>
{/* AppBar */}

View File

@@ -22,7 +22,9 @@ import {
TableHead,
TableRow,
Tooltip,
Typography
Typography,
useMediaQuery,
useTheme
} from '@mui/material';
import {
MdDirectionsBike,
@@ -53,6 +55,7 @@ import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
import RidersRoutes from './RidersRoutes';
import { OpenToast } from 'components/third-party/OpenToast';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
// ============================================================================
// Design tokens — shared with deliveries / tenants / customers / pricing /
@@ -182,6 +185,9 @@ function formatNumberToRupees(value) {
// ==============================|| Riders Summary ||============================== //
export default function RidersSummary() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD'));
const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD'));
const [locaName, setLocoName] = useState('All');
@@ -584,6 +590,249 @@ export default function RidersSummary() {
background: '#fff'
}}
>
{isMobile ? (
<MobileCardList scroll>
{isLoadingReports && (
<Stack alignItems="center" sx={{ py: 4 }}>
<Typography variant="caption" sx={{ color: DT.textMuted, fontWeight: 700 }}>
Loading riders
</Typography>
</Stack>
)}
{(!filteredRows || filteredRows.length === 0) && !isLoadingReports ? (
<Stack alignItems="center" spacing={1.5} sx={{ py: 6 }}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdDirectionsBike size={28} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
No riders to show
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
{searchword ? 'Try a different keyword.' : 'Pick a zone or date range to load the summary.'}
</Typography>
</Stack>
) : (
filteredRows.map((row, index) => {
const isOpen = openRow === row.userid;
const amount = Math.max(Number(row.charges) || 0, Number(row.deliveryamt) || 0);
const riderName = `${row?.firstname || ''} ${row?.lastname || ''}`.trim() || '—';
return (
<MobileCard
key={row.userid || index}
accent={BRAND}
selected={isOpen}
header={
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ minWidth: 0 }}>
<AccentAvatar color={BRAND} size={36}>
<MdPerson size={18} />
</AccentAvatar>
<Stack sx={{ minWidth: 0 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
{riderName}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
#{String(index + 1).padStart(2, '0')} · ID #{row.userid}
</Typography>
</Stack>
</Stack>
<Stack direction="row" spacing={0.75} sx={{ flexShrink: 0 }}>
<Tooltip title="View planned route" placement="top">
<IconButton
size="small"
onClick={() => {
setSelectedRider({
userid: row?.userid,
name: `${row?.firstname || ''} ${row?.lastname || ''}`.trim() || `Rider ${row?.userid}`
});
setLogDetails(null);
setMapOpen(true);
getuserdeliverylogs(row?.userid);
}}
sx={{
bgcolor: soft('#0ea5e9'),
color: '#0ea5e9',
border: `1px solid ${edge('#0ea5e9')}`,
'&:hover': { bgcolor: '#0ea5e9', color: '#fff' }
}}
>
<MdMap size={14} />
</IconButton>
</Tooltip>
<Tooltip title={isOpen ? 'Collapse breakdown' : 'Expand breakdown'} placement="top">
<IconButton
size="small"
onClick={() => {
const isOpening = !isOpen;
setOpenRow(isOpening ? row.userid : null);
if (isOpening) fetchTenantSummary(row.userid);
}}
sx={{
bgcolor: isOpen ? BRAND : soft(BRAND),
color: isOpen ? '#fff' : BRAND,
border: `1px solid ${edge(BRAND)}`,
'&:hover': { bgcolor: BRAND, color: '#fff' }
}}
>
{isOpen ? <MdExpandLess size={16} /> : <MdExpandMore size={16} />}
</IconButton>
</Tooltip>
</Stack>
</Stack>
}
>
<MobileFieldGrid columns={2}>
<MobileField label="Orders">
<CountCell value={row.totalorders} color={BRAND} icon={<MdLocalShipping size={11} />} />
</MobileField>
<MobileField label="Delivered">
<CountCell value={row.delivered} color="#10b981" icon={<MdCheckCircle size={11} />} />
</MobileField>
<MobileField label="Pending">
<CountCell value={row.pending} color="#f59e0b" icon={<MdHourglassEmpty size={11} />} />
</MobileField>
<MobileField label="Cancelled">
<CountCell value={row.cancelled} color="#ef4444" icon={<MdCancel size={11} />} />
</MobileField>
<MobileField label="KMS">
<MetricPill
color="#ef4444"
icon={<MdStraighten size={11} />}
label={`${Number(row.kms || 0).toFixed(2)} km`}
tooltip="KMS"
/>
</MobileField>
<MobileField label="Actual KMS">
<MetricPill
color="#10b981"
icon={<MdStraighten size={11} />}
label={`${Number(row.cumulativekms || 0).toFixed(2)} km`}
tooltip="Actual KMS"
/>
</MobileField>
<MobileField label="Amount" full>
<MetricPill
color={BRAND}
icon={<MdCurrencyRupee size={11} />}
label={formatNumberToRupees(amount).replace('₹', '').trim()}
tooltip="Total Amount"
minWidth={100}
/>
</MobileField>
</MobileFieldGrid>
{/* per-tenant breakdown */}
{isOpen && (
<Collapse in={isOpen} timeout="auto" unmountOnExit>
<Paper
elevation={0}
sx={{
mt: 1.25,
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
overflow: 'hidden',
boxShadow: DT.shadowSoft
}}
>
<Stack
direction="row"
alignItems="center"
spacing={1}
sx={{
px: 1.5,
py: 1,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
borderBottom: `1px solid ${DT.borderSubtle}`
}}
>
<AccentAvatar color={BRAND} size={24} selected>
<MdGroups size={12} />
</AccentAvatar>
<Typography
variant="caption"
sx={{ fontWeight: 800, color: DT.textPrimary, letterSpacing: 0.6, textTransform: 'uppercase' }}
>
Tenant Breakdown
</Typography>
</Stack>
<Stack divider={<Divider />}>
{loading && (
<Typography variant="caption" sx={{ color: DT.textMuted, textAlign: 'center', py: 2 }}>
Loading
</Typography>
)}
{!loading && (!tenantData || tenantData.length === 0) ? (
<Typography variant="caption" sx={{ color: DT.textMuted, textAlign: 'center', py: 2 }}>
No tenant breakdown available.
</Typography>
) : (
tenantData?.map((sub, sidx) => (
<Box key={`${sub.tenantname}-${sidx}`} sx={{ p: 1.5 }}>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mb: 1 }}>
<AccentAvatar color="#0ea5e9" size={24}>
<MdGroups size={12} />
</AccentAvatar>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
{sub.tenantname || '—'}
</Typography>
</Stack>
<MobileFieldGrid columns={2}>
<MobileField label="All">
<CountCell value={sub.totalorders} color={BRAND} icon={<MdLocalShipping size={10} />} />
</MobileField>
<MobileField label="Completed">
<CountCell value={sub.deliveriescompleted} color="#10b981" icon={<MdCheckCircle size={10} />} />
</MobileField>
<MobileField label="Pending">
<CountCell value={sub.deliveriespending} color="#f59e0b" icon={<MdHourglassEmpty size={10} />} />
</MobileField>
<MobileField label="Cancelled">
<CountCell value={sub.deliveriescancelled} color="#ef4444" icon={<MdCancel size={10} />} />
</MobileField>
<MobileField label="KMS">
<MetricPill
color="#ef4444"
icon={<MdStraighten size={10} />}
label={`${Number(sub.kms || 0).toFixed(2)} km`}
tooltip="KMS"
/>
</MobileField>
<MobileField label="Actual KMS">
<MetricPill
color="#10b981"
icon={<MdStraighten size={10} />}
label={`${Number(sub.cumulativekms || 0).toFixed(2)} km`}
tooltip="Actual KMS"
/>
</MobileField>
<MobileField label="Amount" full>
<MetricPill
color={BRAND}
icon={<MdCurrencyRupee size={10} />}
label={formatNumberToRupees(
Math.max(Number(sub.charges) || 0, Number(sub.deliveryamt) || 0)
)
.replace('₹', '')
.trim()}
tooltip="Total Amount"
minWidth={100}
/>
</MobileField>
</MobileFieldGrid>
</Box>
))
)}
</Stack>
</Paper>
</Collapse>
)}
</MobileCard>
);
})
)}
</MobileCardList>
) : (
<TableContainer
sx={{
maxHeight: { xs: 'calc(100vh - 220px)', md: 'calc(100vh - 190px)' },
@@ -940,6 +1189,7 @@ export default function RidersSummary() {
</TableBody>
</Table>
</TableContainer>
)}
{/* ============================================= || Total Bar || ============================================= */}
{filteredRows.length > 0 && (