updated the ui for the mobile view
This commit is contained in:
@@ -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={{
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user