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:
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user