feat: Implement date range picker and enhance order assignment functionality

- Added DateRangePicker component for selecting date ranges in OrderAssignment.
- Updated OrderAssignment to fetch bookings based on selected date range.
- Enhanced status handling in OrderAssignment with new status chip display logic.
- Refactored KPI card implementation in RiderRoutes and Riders to use shared StatCard component.
- Improved ProfileDrawer to retain rider data during close transition.
- Fixed minor text formatting in Routing component.
This commit is contained in:
2026-07-09 16:28:45 +05:30
parent f53578c520
commit ef0b14d254
10 changed files with 497 additions and 150 deletions

View File

@@ -34,7 +34,6 @@ import PedalBikeOutlinedIcon from '@mui/icons-material/PedalBikeOutlined';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import AirportShuttleOutlinedIcon from '@mui/icons-material/AirportShuttleOutlined';
import InventoryOutlinedIcon from '@mui/icons-material/Inventory2Outlined';
import TrendingUpOutlinedIcon from '@mui/icons-material/TrendingUpOutlined';
import PaymentsOutlinedIcon from '@mui/icons-material/PaymentsOutlined';
import AccessTimeOutlinedIcon from '@mui/icons-material/AccessTimeOutlined';
import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined';
@@ -48,6 +47,7 @@ import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
import HomeWorkOutlinedIcon from '@mui/icons-material/HomeWorkOutlined';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
import StatCard from '@/components/StatCard';
import { getMilers, createMiler, updateMiler, deleteMiler } from '@/api/hub';
import { getHubContext } from '@/auth/session';
@@ -228,45 +228,9 @@ function VehicleCell({ vehicle, vehicleNo }) {
);
}
// KPI Card - More spacious
function KpiCard({ icon: Icon, label, value, sub, color, bg, trend }) {
return (
<Card
elevation={0}
sx={{
borderRadius: 2,
border: '1px solid #ECEEF1',
height: '100%',
transition: 'all .2s',
'&:hover': {
boxShadow: '0 10px 30px rgba(0,0,0,0.08)',
transform: 'translateY(-2px)'
}
}}
>
<CardContent sx={{ p: 2.25, '&:last-child': { pb: 2.25 } }}>
{/* Icon + label sit together on a clean top row */}
<Stack direction="row" alignItems="center" spacing={1.75} sx={{ mb: 1.5 }}>
<Avatar variant="rounded" sx={{ bgcolor: bg, color, width: 40, height: 40, borderRadius: 2, flexShrink: 0 }}>
<Icon sx={{ fontSize: 21 }} />
</Avatar>
<Typography sx={{ fontSize: '0.72rem', color: '#6C757D', fontWeight: 700, letterSpacing: 0.6, textTransform: 'uppercase', lineHeight: 1.3 }}>
{label}
</Typography>
</Stack>
<Typography sx={{ fontSize: '1.6rem', fontWeight: 800, color: '#1A1A2E', lineHeight: 1.15, mb: 0.5 }}>
{value}
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75}>
{trend && <TrendingUpOutlinedIcon sx={{ fontSize: 15, color: '#1E8E3E', flexShrink: 0 }} />}
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 500 }}>{sub}</Typography>
</Stack>
</CardContent>
</Card>
);
}
// KPI card — the shared StatCard is the single reference design (see components/StatCard).
// Kept as a thin alias so the call sites below stay unchanged; `hover` on for the lift effect.
const KpiCard = (props) => <StatCard {...props} hover />;
// ════════════════════════════════════════════════════════════════════════════════
// Dialogs (Cleaner & Better Spaced)
@@ -494,18 +458,29 @@ function RiderFormDialog({ open, onClose, onSave, initial, mode }) {
}
// Profile Drawer - view + inline edit, in the same right-side sheet
function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
function ProfileDrawer({ rider: riderProp, onClose, onSave, startInEdit = false }) {
const [editing, setEditing] = useState(startInEdit);
const [form, setForm] = useState(rider || {});
// Retain the last opened miler so the sheet keeps rendering its content while it
// slides OUT — the prop becomes null on close, and unmounting here (an early
// `return null`) would kill the exit animation and make closing feel instant.
const [retained, setRetained] = useState(riderProp);
const [form, setForm] = useState(riderProp || {});
// Reset the editable copy + mode whenever a different miler is opened.
useEffect(() => {
setForm(rider || {});
setEditing(startInEdit);
}, [rider, startInEdit]);
if (riderProp) {
setRetained(riderProp);
setForm(riderProp);
setEditing(startInEdit);
}
}, [riderProp, startInEdit]);
if (!rider) return null;
const sr = successRate(rider);
const open = Boolean(riderProp); // drives the slide; false triggers the exit anim
const rider = riderProp || retained; // keep content during the close transition
// NOTE: we deliberately do NOT early-return when there's no rider. The Drawer stays
// mounted (open=false) from first render, so the first click is a real false→true
// transition and animates — otherwise the first open mounts already-open and skips it.
const sr = rider ? successRate(rider) : 0;
const setField = (key) => (e) => setForm((f) => ({ ...f, [key]: e.target.value }));
@@ -519,15 +494,17 @@ function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
return (
<Drawer
anchor="right"
open={!!rider}
open={open}
onClose={onClose}
// Slow the slide a touch (default ~225ms feels abrupt) for a smoother open/close.
transitionDuration={{ enter: 400, exit: 340 }}
sx={{
// This build renders temporary drawers at zIndex.drawer (1200), which sits
// BELOW the app bar (drawer + 1). On mobile the sheet starts at top:0, so its
// Close button would hide under the app bar. Lift the whole modal above it.
zIndex: (t) => t.zIndex.modal,
'& .MuiDrawer-paper': {
width: { xs: '100%', sm: 480 },
width: { xs: '100%', sm: 520 },
maxWidth: '100%',
// Run the sheet the full height of the viewport so there is no empty
// strip above the red header (zIndex.modal keeps Close clickable).
@@ -541,6 +518,7 @@ function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
},
}}
>
{rider && (<>
{/* Header */}
<Box sx={{ background: 'linear-gradient(135deg, #C01227 0%, #8D0E1D 100%)', px: 2.75, py: 2.25, color: '#fff', flexShrink: 0 }}>
<Button onClick={onClose} startIcon={<ArrowBackRoundedIcon />} size="small"
@@ -605,7 +583,7 @@ function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
<Grid container spacing={1.75}>
{/* Today's Performance */}
<Grid item xs={12}>
<Grid size={12}>
<Typography variant="overline" sx={{ color: '#6C757D', fontWeight: 700, letterSpacing: 1, display: 'block', mb: 1.5, fontSize: '0.75rem' }}>
TODAY'S PERFORMANCE
</Typography>
@@ -615,28 +593,22 @@ function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
{ label: "Failed", value: rider.deliveriesFailed, icon: WarningAmberOutlinedIcon, color: "#DC2626", bg: "#FEE2E2" },
{ label: "COP", value: inr(rider.codCollected), icon: PaymentsOutlinedIcon, color: "#D97706", bg: "#FEF3C7" },
].map((stat, i) => (
<Grid item xs={4} key={i}>
<Paper elevation={0} sx={{ p: 2, borderRadius: 2, border: '1px solid #E2E8F0', bgcolor: '#FFFFFF', height: '100%' }}>
<Box sx={{ p: 1, borderRadius: 2, bgcolor: stat.bg, display: 'inline-flex', mb: 1.25 }}>
<stat.icon sx={{ fontSize: 22, color: stat.color }} />
</Box>
<Typography variant="body2" sx={{ color: '#6C757D', display: 'block', lineHeight: 1.2 }}>{stat.label}</Typography>
<Typography sx={{ fontSize: '1.35rem', fontWeight: 800, color: '#1E293B', mt: 0.25, lineHeight: 1.1 }}>{stat.value}</Typography>
</Paper>
<Grid size={4} key={i}>
<StatCard icon={stat.icon} label={stat.label} value={stat.value} color={stat.color} bg={stat.bg} />
</Grid>
))}
</Grid>
</Grid>
{/* Live Load Panel */}
<Grid item xs={12}>
<Grid size={12}>
<Paper elevation={0} sx={{ p: 2, borderRadius: 2, border: '1px solid #E2E8F0', bgcolor: '#FFFFFF' }}>
<Typography variant="overline" sx={{ color: '#6C757D', fontWeight: 700, letterSpacing: 1, display: 'block', mb: 1.75, fontSize: '0.75rem' }}>
LIVE LOAD
</Typography>
<CapacityBar rider={rider} showLabel />
<Divider sx={{ my: 2 }} />
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ gap: 2, width: '100%' }}>
<Typography variant="body2" sx={{ color: '#6C757D', fontWeight: 600 }}>Pending pickups</Typography>
<Typography sx={{ fontSize: '1.5rem', fontWeight: 800, color: '#1E293B', lineHeight: 1 }}>{rider.pickupsPending}</Typography>
</Stack>
@@ -644,7 +616,7 @@ function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
</Grid>
{/* Miler Details Panel */}
<Grid item xs={12}>
<Grid size={12}>
<Paper elevation={0} sx={{ p: 2, borderRadius: 2, border: '1px solid #E2E8F0', bgcolor: '#FFFFFF' }}>
<Typography variant="overline" sx={{ color: '#6C757D', fontWeight: 700, letterSpacing: 1, display: 'block', mb: 2, fontSize: '0.75rem' }}>
MILER DETAILS
@@ -657,7 +629,7 @@ function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
{ icon: BadgeOutlinedIcon, label: "Vehicle", value: `${rider.vehicle} • ${rider.vehicleNo}` },
{ icon: AccessTimeOutlinedIcon, label: "Check-in", value: `${rider.checkInTime} (${rider.hoursToday}h)` },
].map((detail, idx) => (
<Grid item xs={6} key={idx}>
<Grid size={6} key={idx}>
<Stack direction="row" spacing={1.5} alignItems="flex-start">
<detail.icon sx={{ color: '#94A3B8', fontSize: 20, mt: '2px' }} />
<Box sx={{ minWidth: 0 }}>
@@ -697,6 +669,7 @@ function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
</>
)}
</Box>
</>)}
</Drawer>
);
}
@@ -1358,7 +1331,7 @@ export default function Riders() {
<TableCell><VehicleCell vehicle={rider.vehicle} vehicleNo={rider.vehicleNo} /></TableCell>
<TableCell><CapacityBar rider={rider} /></TableCell>
<TableCell>
<Stack direction="row" gap={3}>
<Stack direction="row" spacing={4}>
<Box>
<Typography variant="caption" color="text.secondary">Done</Typography>
<Typography fontWeight={700} color="#1E8E3E">{rider.deliveriesDone}</Typography>
@@ -1385,7 +1358,7 @@ export default function Riders() {
<Box sx={{ p: { xs: 2.5, md: 4 } }}>
<Grid container spacing={{ xs: 2.5, md: 3 }}>
{filtered.map(r => (
<Grid item xs={12} sm={6} lg={4} key={r.id}>
<Grid size={{ xs: 12, sm: 6, lg: 4 }} key={r.id}>
<RiderCard rider={r} onView={openView} onMenu={openMenu} />
</Grid>
))}