import logger from '../../../utils/logger'; import { enqueueSnackbar } from 'notistack'; import { DeleteFilled, EditOutlined } from '@ant-design/icons'; import { useState, useEffect, Fragment, useRef, useMemo } from 'react'; import dayjs from 'dayjs'; var utc = require('dayjs/plugin/utc'); dayjs.extend(utc); import axios from 'axios'; import { useTheme } from '@mui/material/styles'; import { MdOutlineDateRange, MdAccessTime, MdAllInclusive, MdLightMode, MdWbSunny, MdNightsStay, MdCheck, MdStorefront, MdLocationOn, MdDirectionsBike, MdNotificationsActive, MdPersonPin, MdHistoryToggleOff, MdCheckCircle, MdCancel, MdInventory2, MdHourglassEmpty, MdTune, MdMyLocation, MdOutlineLocalShipping, MdOutlinePendingActions, MdOutlineCheckCircle, MdOutlineCancel } from 'react-icons/md'; import { useQuery, useMutation, useInfiniteQuery } from '@tanstack/react-query'; import { Avatar, Box, Button, Chip, Grid, IconButton, Stack, Typography, Table, TableCell, TableBody, TableHead, Collapse, Dialog, TableRow, DialogContent, DialogTitle, Tooltip, DialogActions, Checkbox, Autocomplete, TextField, TableContainer, Backdrop, MenuItem, Menu, Paper, useMediaQuery } from '@mui/material'; import { PopupTransition } from 'components/@extended/Transitions'; import { addDays, addMonths, addWeeks, endOfMonth, endOfWeek, startOfMonth, startOfWeek } from 'date-fns'; import { DateRangePicker } from 'mui-daterange-picker'; import * as React from 'react'; import Loader from 'components/Loader'; import { KeyboardArrowDownOutlined, KeyboardArrowUpOutlined } from '@mui/icons-material'; import { cancelDeliveryAPI, changeRiderAPI, fetchCountAPI, fetchDeliveries, fetchRidersList, notifyRider, updateDeliveryAPI, getorderdetails, gettenantlocations, getTenants } from 'pages/api/api'; import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar'; import { OpenToast } from 'components/third-party/OpenToast'; import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton'; import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; import LoaderWithImage from 'components/nearle_components/LoaderWithImage'; import PageHeader from 'components/nearle_components/PageHeader'; import StatCard from 'components/nearle_components/StatCard'; import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard'; // ============================================================================ // Design tokens — extracted from the polished "Batch" dropdown so every // surface on this page (filters, KPIs, tabs, status badges, dialogs) shares // the same visual language. All helpers take a color and emit MUI sx values. // ============================================================================ const DT = { radiusPill: 999, radiusCard: 14, radiusInner: 10, radiusField: 10, // Restrained, low-contrast elevation — corporate (Linear/Stripe), not flashy. shadowSoft: '0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.05)', shadowMd: '0 1px 3px rgba(15, 23, 42, 0.06)', shadowPop: '0 12px 32px rgba(15, 23, 42, 0.12)', textPrimary: '#0f172a', textSecondary: '#64748b', textMuted: '#94a3b8', borderSubtle: '#e2e8f0', borderHover: '#cbd5e1', divider: '#f1f5f9', surface: '#ffffff', surfaceAlt: '#f8fafc', brand: '#C01227' }; // Quick alpha helpers (hex + percentage suffix). Mirrors the batch-dropdown // pattern (`${color}08`, `${color}18`, `${color}55`, `${color}26`). const a = (c, suffix) => `${c}${suffix}`; const tint = (c) => a(c, '08'); // very subtle surface tint const soft = (c) => a(c, '18'); // soft chip / avatar bg const ring = (c) => a(c, '26'); // focus ring color const edge = (c) => a(c, '55'); // resting border // Pill input sx — used by every filter Autocomplete/TextField on the page. // Accepts the accent color and returns sx for the outer TextField. Width is // driven by parent flex/grid so this helper stays width-agnostic. // Neutral, corporate filter field: white surface, hairline border, brand focus // ring. Colour is no longer used to tint the whole control (that produced the // "rainbow" filter bar) — accent now lives only in the small start-adornment // icon, which aids scanning without flooding the surface. const pillFieldSx = () => ({ cursor: 'pointer', '& .MuiOutlinedInput-root': { borderRadius: DT.radiusField + 'px', bgcolor: DT.surface, fontWeight: 600, color: DT.textPrimary, paddingRight: '8px', cursor: 'pointer', transition: 'border-color 0.15s, box-shadow 0.15s', '& fieldset': { borderColor: DT.borderSubtle, borderWidth: 1 }, '&:hover fieldset': { borderColor: DT.borderHover }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(DT.brand)}` }, '&.Mui-focused fieldset': { borderColor: DT.brand, borderWidth: 1.5 } }, '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: DT.textMuted } }); // Status palette — drives tab pills, row status badges, dialogs. Keys are // Doormile's real booking statuses, lowercased (row.status is compared // lowercased everywhere below so exact backend casing doesn't matter). const STATUS_META = { all: { label: 'All', color: '#94a3b8', icon: MdAllInclusive }, pending_pickup: { label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty }, miler_assigned: { label: 'Assigned', color: '#6366f1', icon: MdPersonPin }, pickup_scheduled: { label: 'Scheduled', color: '#06b6d4', icon: MdHistoryToggleOff }, at_customer: { label: 'At Customer', color: '#8b5cf6', icon: MdLocationOn }, picked_up: { label: 'Picked Up', color: '#14b8a6', icon: MdInventory2 }, at_hub: { label: 'At Hub', color: '#0ea5e9', icon: MdStorefront }, delivered: { label: 'Delivered', color: '#10b981', icon: MdCheckCircle }, cancelled: { label: 'Cancelled', color: '#ef4444', icon: MdCancel } }; // Ordered status list driving the tabs row (left → right). `status` is the // lowercased key into STATUS_META/batchCounts; `apiStatus` is the exact // casing Doormile's /admin/bookings status filter expects. const STATUS_TABS = [ { status: 'all', apiStatus: 'all', countKey: 'all' }, { status: 'pending_pickup', apiStatus: 'Pending_Pickup', countKey: 'pending_pickup' }, { status: 'miler_assigned', apiStatus: 'Miler_Assigned', countKey: 'miler_assigned' }, { status: 'pickup_scheduled', apiStatus: 'Pickup_Scheduled', countKey: 'pickup_scheduled' }, { status: 'at_customer', apiStatus: 'At_Customer', countKey: 'at_customer' }, { status: 'picked_up', apiStatus: 'Picked_Up', countKey: 'picked_up' }, { status: 'at_hub', apiStatus: 'At_Hub', countKey: 'at_hub' }, { status: 'delivered', apiStatus: 'Delivered', countKey: 'delivered' }, { status: 'cancelled', apiStatus: 'Cancelled', countKey: 'cancelled' } ]; // KPI palette + icons — mirrors the four cards across the top of the page. const KPI_META = [ { key: 'created', label: 'Created Orders', color: '#0ea5e9', icon: MdOutlineLocalShipping }, { key: 'pending', label: 'Pending Orders', color: '#f59e0b', icon: MdOutlinePendingActions }, { key: 'delivered', label: 'Delivered Orders', color: '#10b981', icon: MdOutlineCheckCircle }, { key: 'cancelled', label: 'Cancelled Orders', color: '#ef4444', icon: MdOutlineCancel } ]; // Batches mirror the dispatch page's slot definitions so an operator who // segments the day there sees the same buckets here. Hours are 24h, half-open // [startHour, endHour) — a delivery at exactly endHour falls into the *next* // batch (or none, if the gap isn't covered). const BATCH_OPTIONS = [ { id: 'all', label: 'All Batches', range: 'Across the day', color: '#7c3aed', iconKey: 'all' }, { id: 'morning', label: 'Morning Batch', range: '12 AM to 8 AM', color: '#0ea5e9', iconKey: 'morning', startHour: 0, endHour: 8 }, { id: 'afternoon', label: 'Afternoon Batch', range: '9 AM to 12:30 PM', color: '#f59e0b', iconKey: 'afternoon', startHour: 9, endHour: 12.5 }, { id: 'evening', label: 'Evening Batch', range: '4 PM to 7 PM', color: '#6366f1', iconKey: 'evening', startHour: 16, endHour: 19 } ]; // Per-batch icon components — kept separate from BATCH_OPTIONS so the option // objects stay serializable / printable. Looked up at render time by iconKey. const BATCH_ICONS = { all: MdAllInclusive, morning: MdLightMode, afternoon: MdWbSunny, evening: MdNightsStay }; // Auto-pick the batch matching the operator's LOCAL wall-clock hour so the // page lands them on the slot they're most likely curious about. Falls back // to 'all' when the current hour lies in a configured gap (8–9 AM, 12 PM–4 // PM, after 7 PM). Local time, not UTC, to match the dispatch page's // bucketing — both pages must agree on which batch a given row belongs to. const detectInitialBatchId = () => { const now = dayjs(); const h = now.hour() + now.minute() / 60; for (const b of BATCH_OPTIONS) { if (b.id === 'all') continue; if (h >= b.startHour && h < b.endHour) return b.id; } return 'all'; }; // Bucket by `assigntime` only — matches the spec ("bucketed by assigntime") // and the dispatch page's default time-field selection (`Dispatch.js:763` // initialises `selectedTimeField` to `'assigned'`, whose `keys` is just // `['assigntime']`). The previous priority cascade started with // `deliverytime`, which mis-bucketed delivered orders into the wave they // were *completed* in rather than the wave they were *assigned to* — so an // order assigned at 07:00 (Morning) but delivered at 11:00 fell into the // 8–9 AM / 12 PM–4 PM gap and silently disappeared from every batch on this // page while still showing up on dispatch's Morning Batch. Rows without an // `assigntime` (unassigned pending orders) return null and intentionally // don't bucket — matches dispatch. const BATCH_TIME_KEYS = ['assigntime']; // Returns one of the batch ids in BATCH_OPTIONS (excluding 'all'), or null // when the row has no usable timestamp / falls in a gap between batches. const getRowBatchId = (row) => { let t = null; for (const k of BATCH_TIME_KEYS) { if (row?.[k]) { t = row[k]; break; } } if (!t) return null; const str = String(t).trim(); // Bare YYYY-MM-DD with no time component would always parse to midnight and // get mis-bucketed into Morning — skip those. if (/^\d{4}-\d{2}-\d{2}$/.test(str)) return null; // Parse in LOCAL time to match the dispatch page's bucketing exactly // (`Dispatch.js:218`). Dispatch never uses `.utc()` for batch assignment, // so a row that lands in Evening Batch on the dispatch page must also land // in Evening Batch here — otherwise an operator running in IST/PST/etc. // sees different totals on the two pages for the same underlying rows. const d = dayjs(t); if (!d.isValid()) return null; const h = d.hour() + d.minute() / 60; for (const b of BATCH_OPTIONS) { if (b.id === 'all') continue; if (h >= b.startHour && h < b.endHour) return b.id; } return null; }; // Shared Paper used by every Autocomplete popup on the page. Matches the // batch dropdown's soft elevated look. const SoftPaper = (props) => ( ); // Renders a colored avatar with an icon — used in pill input adornments and // in dropdown rows. Tracks `selected` so it can flip between filled and // soft variants identically to the batch dropdown. const AccentAvatar = ({ color, selected, size = 24, children }) => ( {children} ); // ================================================= || deliveries (initial point)|| ================================================= const Deliveries = () => { const userid = localStorage.getItem('userid'); const theme = useTheme(); // Below `md` we swap the wide data table for an app-style card list. Desktop // (md and up) keeps the exact same table — no behaviour change either way. const isMobile = useMediaQuery(theme.breakpoints.down('md')); const loadMoreRef = useRef(); const containerRef = useRef(); const [deliverylist, setDeliverylist] = useState([]); const [dialogopen, setDialogopen] = useState(false); const [locaName, setLocoName] = useState('All'); const [appId, setAppId] = useState(0); const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD')); const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD')); const [tabstatus, setTabstatus] = useState('All'); const [tabvalue, setTabvalue] = useState(0); const [open, setOpen] = useState(false); const [kms, setKms] = useState(''); const [cumulativekms, setCumulativeKms] = useState(); const [deliveryamount, setDeliveryamount] = useState(); const [notes, setNotes] = useState(''); const [currentorder, setCurrentorder] = useState({}); const [deliverylat, setDeliverylat] = useState(''); const [deliverylong, setDeliverylong] = useState(''); const [currentStatus, setCurrentStatus] = useState('all'); const [updateStatus, setUpdateStatus] = useState('delivered'); const locationRef = useRef(null); const tenantRef = useRef(null); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(50); const [productCollapse, setProductCollapse] = useState(null); const [orderHeaderid, setOrderHeaderId] = useState(null); const [searchword, setSearchword] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [menuAnchorEl, setMenuAnchorEl] = React.useState(null); const [selectedRow, setSelectedRow] = useState(null); const [loading1, setLoading1] = useState(false); const [cancelDeliveryOpen, setCancelDeliveryOpen] = useState(false); const [changeDialogOpen, setChangeDialogOpen] = useState(false); const [cancelFeed, setCancelFeed] = useState(''); const [selectedRider, setSelectedRider] = useState(null); const [tenantid, setTenantid] = useState(0); const [locationid, setLocationid] = useState(0); const [tenantValue, setTenantValue] = useState(null); const [locationValue, setLocationValue] = useState(null); const [riderid, setRiderid] = useState(0); // Selected batch id — drives the client-side row filter. Defaults to the // batch matching the current UTC hour (the operator is most likely curious // about "now"); never `null` since there's no longer an "All" option. const [selectedBatch, setSelectedBatch] = useState(detectInitialBatchId); const roleid = localStorage.getItem('roleid'); useEffect(() => { setTenantid(0); setTenantValue(null); setLocationid(0); setLocationValue(null); }, [appId]); // to clear the location autocomplete useEffect(() => { setLocationid(0); setLocationValue(null); }, [tenantid]); const menuOpen = Boolean(menuAnchorEl); const handleMenuOpen = (event, row) => { setSelectedRow(row); console.log('selectedRow', row); setMenuAnchorEl(event.currentTarget); }; const handleMenuClose = () => { setMenuAnchorEl(null); }; // =========================================== || cancelDelivery || =========================================== const { mutate: cancelDelivery } = useMutation({ mutationFn: ({ selectedRow, cancelFeed }) => cancelDeliveryAPI(selectedRow, cancelFeed), onSuccess: () => { opentoast('Delivery Cancelled Successfully', 'success'); setCancelDeliveryOpen(false); fetchCountRefetch(); // Refresh count data fetchDeliveriesRefetch(); // Refresh deliveries countSourceRefetch(); // Refresh the all-statuses dataset feeding table + chips }, onError: (error) => { opentoast(error.message, 'error'); } }); // ==============================|| cancelridernotification ||============================== // const cancelridernotification = async () => { console.log('cancelridernotification', selectedRow); try { const response = await axios.post(`${process.env.REACT_APP_URL}/utils/notifyuser`, { token: selectedRow.userfcmtoken, notification: { title: 'NearleXpress', body: `${selectedRow.orderid} have been Cancelled`, sound: 'ring', image: '' }, data: { type: 'cancel' } }); return response.data; } catch (err) { opentoast(err.message, 'error', 2000); console.log(err); } }; // ==============================|| getTenants ||============================== // const { data: tenantlist, isLoading: fetchtenantsIsLoading, isError: fetchtenantsIsError, error: fetchtenantsError } = useQuery({ queryKey: ['tenantlist', appId], queryFn: () => getTenants(appId), // Ensure appId is passed enabled: appId !== 0 // Ensures query runs only when appId is valid }); // ==============================|| gettenantlocations ||============================== // const { data: locationlist, isLoading: fetchlocationsIsLoading, isError: fetchlocationsIsError, error: fetchlocationsError } = useQuery({ queryKey: ['gettenantlocations', tenantid], queryFn: () => gettenantlocations(tenantid), // Ensure appId is passed enabled: tenantid !== 0 // Ensures query runs only when appId is valid }); // =========================================== || notifyrider || =========================================== const notifyRiderMutation = useMutation({ mutationFn: notifyRider, // Using the separate function onSuccess: () => { enqueueSnackbar('Notification sent Successfully', { variant: 'success', anchorOrigin: { vertical: 'top', horizontal: 'right' }, autoHideDuration: 2000 }); }, onError: (error) => { enqueueSnackbar(error.message, { variant: 'error', anchorOrigin: { vertical: 'top', horizontal: 'right' }, autoHideDuration: 2000 }); } }); // =========================================== || changerider || =========================================== const changeRiderMutation = useMutation({ mutationFn: ({ selectedRider, selectedRow }) => changeRiderAPI(selectedRider, selectedRow), onSuccess: (res, { selectedRider, selectedRow }) => { setLoading1(false); setChangeDialogOpen(false); if (res.data.message === 'Success') { logger.info(`Rider changed successfully for order ID ${selectedRow?.orderid}. New Rider: ${selectedRider?.firstname} ${selectedRider?.lastname}`); opentoast('Rider Changed Successfully', 'success'); } fetchCountRefetch(); // Refresh count data fetchDeliveriesRefetch(); // Refresh deliveries countSourceRefetch(); // Refresh the all-statuses dataset feeding table + chips notifyRiderMutation.mutate(selectedRider.userfcmtoken); }, onError: (err, { selectedRow }) => { logger.error(`Failed to change rider for order ID ${selectedRow?.orderid}:`, err); opentoast(err.message, 'error'); setLoading1(false); } }); /* ============================================= || opentoast || ============================================= */ const opentoast = (message, variant) => { enqueueSnackbar(message, { variant, anchorOrigin: { vertical: 'top', horizontal: 'right' }, autoHideDuration: 2000 }); }; // ==============================|| getorderdetails ||============================== // const { data: orderdetails } = useQuery({ queryKey: ['orderdetails', orderHeaderid], queryFn: () => getorderdetails(orderHeaderid), enabled: !!orderHeaderid // ✅ prevent initial fetch when undefined }); const dialogclose = () => { setDialogopen(false); }; const handleChangetab = (e, i) => { setPage(0); setTabvalue(i); setRowsPerPage(50); const tab = STATUS_TABS[i]; setTabstatus(STATUS_META[tab.status]?.label || tab.status); setCurrentStatus(tab.apiStatus); setSearchword(''); }; const okclicked = () => { setOpen(false); }; /* ============================================= || fetchDeliveries | ============================================= */ const { isLoading: fetchDeliveriesIsLoading, isError: fetchDeliveriesIsError, error: fetchDeliveriesError, refetch: fetchDeliveriesRefetch } = useInfiniteQuery({ queryKey: [ 'fetchdeliveries', appId, userid, currentStatus, startdate, enddate, rowsPerPage, debouncedSearch, tenantid, locationid, riderid ], queryFn: fetchDeliveries, getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined }); // (filteredRows is defined below, after countSourceRows / countSourceLoading // are in scope — they're the single source of truth for both the table and // the per-status chip counts.) // Parallel "all-statuses" query used ONLY to derive batch-aware tab counts. // The primary `fetchdeliveries` query above is keyed by `currentStatus`, so // its rows are scoped to the active tab — they can't tell us how many // delivered/cancelled/etc. exist in the same batch. We fetch the full day // once (status='all'), keep it cached, and re-derive the counts per status // whenever the operator switches batches. Search keyword is intentionally // dropped from this key so the chip counts reflect the batch totals rather // than the search-filtered subset. const { data: countSourceData, fetchNextPage: countFetchNext, hasNextPage: countHasNext, isFetchingNextPage: countIsFetchingNext, isLoading: countSourceIsLoading, refetch: countSourceRefetch } = useInfiniteQuery({ queryKey: [ 'fetchdeliveries-batchcounts', appId, userid, 'all', startdate, enddate, 200, '', tenantid, locationid, riderid ], queryFn: fetchDeliveries, getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined }); const countSourceRows = useMemo( () => (countSourceData?.pages || []).flatMap((p) => p.rows || []), [countSourceData] ); useEffect(() => { if (countHasNext && !countIsFetchingNext) countFetchNext(); }, [countHasNext, countIsFetchingNext, countFetchNext, countSourceRows.length]); // Loading flag for the table empty state — true while we're still streaming // pages, so we don't show "No Orders" prematurely before the full // day's rows have arrived. Goes false once auto-pagination drains. const countSourceLoading = countHasNext || countIsFetchingNext; // The table now reads from the same source as the chip counts. That way the // "74" in the Delivered chip and the rows shown in the table can never // disagree (they used to, because the table consumed a separate per-status // paginated query while the chips read from the full all-statuses dataset). // Three filter steps: // 1. Batch — selected via the dropdown. 'all' bypasses this filter. // 2. Tab status — currentStatus comes from handleChangetab. // 3. Search — client-side substring match across customer/address/order // fields. Server-side search isn't needed since every row is loaded. const filteredRows = useMemo(() => { const wantStatus = String(currentStatus || '').toLowerCase(); const q = String(debouncedSearch || '').trim().toLowerCase(); return countSourceRows.filter((r) => { if (selectedBatch !== 'all' && getRowBatchId(r) !== selectedBatch) return false; const s = String(r.status || '').toLowerCase(); if (wantStatus && wantStatus !== 'all' && s !== wantStatus) return false; if (q) { const hay = [r.bookingreference, r.bookingid, r.pickupaddress, r.deliveryaddress, r.assignedmileruserid] .map((v) => String(v || '').toLowerCase()) .join(' '); if (!hay.includes(q)) return false; } return true; }); }, [countSourceRows, selectedBatch, currentStatus, debouncedSearch]); // Counts per status, scoped to the selected batch. Keys mirror the legacy // *Length keys returned by fetchCountAPI so the JSX swap-in is mechanical // (countData?.uncoveredLength → batchCounts.uncoveredLength). const batchCounts = useMemo(() => { const c = {}; countSourceRows.forEach((r) => { if (selectedBatch !== 'all' && getRowBatchId(r) !== selectedBatch) return; const s = String(r.status || '').toLowerCase(); c[s] = (c[s] || 0) + 1; c.all = (c.all || 0) + 1; }); return c; }, [countSourceRows, selectedBatch]); // Per-batch total (any status) for the dropdown badges. Computed once for // every batch + 'all' so each option in the menu can show its own count // without re-walking the list. Mirrors dispatch's batchCounts shape // (`Dispatch.js:1198–1206`) — totals here should match the per-batch numbers // visible on the dispatch page for the same date. const batchTotals = useMemo(() => { // `all` is the SUM of the three batch buckets — not the raw row count. // Rows that fall in a gap (8–9 AM, 12:30–4 PM, after 7 PM) or have no // `assigntime` belong to no batch, so they're excluded here too. This keeps // the "All Batches" badge equal to Morning + Afternoon + Evening // (e.g. 36 + 89 + 53 = 178) instead of over-reporting the un-bucketed total. const totals = { all: 0 }; BATCH_OPTIONS.forEach((b) => { if (b.id === 'all') return; totals[b.id] = 0; }); countSourceRows.forEach((r) => { const id = getRowBatchId(r); if (id) { totals[id] = (totals[id] || 0) + 1; totals.all += 1; } }); return totals; }, [countSourceRows]); // IntersectionObserver now watches the count-source query — that's the // single source of truth for both the table and the chip counts. When the // sentinel scrolls into view it nudges the next page of all-statuses // deliveries to keep the dataset complete. useEffect(() => { if (!countHasNext) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) { countFetchNext(); } }, { root: document.querySelector('.MuiTableContainer-root'), rootMargin: '0px', threshold: 1.0 } ); if (loadMoreRef.current) observer.observe(loadMoreRef.current); return () => { if (loadMoreRef.current) observer.unobserve(loadMoreRef.current); }; }, [countHasNext, countFetchNext]); const handleScroll = (event) => { const { scrollTop, scrollHeight, clientHeight } = event.currentTarget; if (scrollTop + clientHeight >= scrollHeight - 50) { if (countHasNext && !countIsFetchingNext) { countFetchNext(); } } }; /* ============================================= || fetchPercentageAPI | ============================================= */ /* ============================================= || fetchcount | ============================================= */ const { data: countData = {}, // Default to empty object isLoading: fetchCountIsLoading, isError: fetchCountIsError, error: fetchCountError, refetch: fetchCountRefetch } = useQuery({ queryKey: ['fetchCountData', appId, userid, startdate, enddate, rowsPerPage, debouncedSearch, tenantid, locationid, riderid, tabstatus], queryFn: () => fetchCountAPI(appId, userid, startdate, enddate, rowsPerPage, debouncedSearch, tenantid, locationid, riderid) }); // ==============================|| fetchRidersList ||============================== // const { data: ridersList = [], isLoading: riderListIsLoading, isError: ridersListIsError, error: ridersListError } = useQuery({ queryKey: ['ridersList', appId], // Unique key for caching & re-fetching queryFn: fetchRidersList, enabled: Boolean(appId), onError: (err) => { OpenToast(err.message, 'error', 2000); } }); /* ============================================= || updatedelivery | ============================================= */ const updateDeliveryMutation = useMutation({ mutationFn: (orderData) => updateDeliveryAPI(orderData), onSuccess: (res) => { console.log(res); if (res.data.status) { opentoast('Updated Successfully', 'success'); setDeliveryamount(''); setNotes(''); setDialogopen(false); fetchDeliveriesRefetch(); fetchCountRefetch(); countSourceRefetch(); } }, onError: (err) => { console.log(err); opentoast(err.message, 'success'); } }); const errorMessage = fetchDeliveriesIsError ? `Error fetching deliveries: ${fetchDeliveriesError?.message}` : fetchCountIsError ? `Error fetching count summary: ${fetchCountError?.message}` : ridersListIsError ? `Error fetching riders: ${ridersListError?.message}` : fetchtenantsIsError ? `Error tenant list: ${fetchtenantsError?.message}` : fetchlocationsIsError ? `Error location list: ${fetchlocationsError?.message}` : null; if (errorMessage) { console.log('errorMessage', errorMessage); OpenToast(errorMessage, 'error', 2000); return null; // or return <> if inside a component } return ( <> {(fetchCountIsLoading || countSourceIsLoading || fetchtenantsIsLoading || fetchlocationsIsLoading || riderListIsLoading) && ( <> {/* */} )} { theme.zIndex.drawer + 1 }} open={ fetchCountIsLoading || fetchDeliveriesIsLoading || fetchtenantsIsLoading || fetchlocationsIsLoading || riderListIsLoading } > {/* */} } {/* ============================================= || Header | ============================================= */} } placeholder="Select Zone" paperComponent={SoftPaper} sx={{ width: { xs: '100%', sm: 280 }, zIndex: 100 }} /> } /> {/* ============================================= || KPI Cards | ============================================= */} {[ { ...KPI_META[0], value: batchTotals.all, percentage: null }, { ...KPI_META[1], value: countData?.uncoveredLength, percentage: countData?.total ? Math.round((countData.uncoveredLength / countData.total) * 100) : 0 }, { ...KPI_META[2], value: countData?.coveredLength, percentage: countData?.total ? Math.round((countData.coveredLength / countData.total) * 100) : 0 }, { ...KPI_META[3], value: countData?.cancelLength, percentage: countData?.total ? Math.round((countData.cancelLength / countData.total) * 100) : 0 } ].map((item) => { const Icon = item.icon; const pct = typeof item.percentage === 'number' ? item.percentage : Number(item.percentage); const hasPct = !Number.isNaN(pct) && item.percentage !== undefined && item.percentage !== null; return ( } color={item.color} loading={fetchCountIsLoading} caption={hasPct ? `${Math.abs(pct)}% of total` : undefined} /> ); })} {/* ============================================= || Filter Bar | ============================================= */} Filters {/* Batch dropdown — replaces the legacy "Deliveries-{datestatus}" and "Orders-All" pills. Filters the loaded rows by the slot the operator picks; defaults to 'all'. Mirrors the dispatch page's batch model. UI mirrors the polished Absent Riders dropdown from the orders page — custom Paper, per-batch color/icon, selected indicator. */} {(() => { const activeBatch = BATCH_OPTIONS.find((b) => b.id === selectedBatch) || BATCH_OPTIONS[0]; const ActiveIcon = BATCH_ICONS[activeBatch.iconKey] || MdAccessTime; return ( opts} options={BATCH_OPTIONS} value={activeBatch} onChange={(e, val) => val && setSelectedBatch(val.id)} getOptionLabel={(option) => option.label} isOptionEqualToValue={(option, value) => option.id === value.id} PaperComponent={SoftPaper} ListboxProps={{ sx: { py: 0, maxHeight: 360 } }} renderOption={(props, option, { selected }) => { const Icon = BATCH_ICONS[option.iconKey] || MdAccessTime; const total = batchTotals[option.id] ?? 0; return (
  • {option.label} {option.range} {/* Per-batch total — same number you see next to each batch on the dispatch page. Lets the operator eyeball-compare without summing the per-status chip counts on the tabs. */} 0 ? `${option.color}18` : '#f1f5f9', color: total > 0 ? option.color : '#94a3b8', border: `1px solid ${total > 0 ? option.color + '55' : '#e2e8f0'}`, '& .MuiChip-label': { px: 0.75 } }} /> {selected && ( )}
  • ); }} renderInput={(params) => (
    ) }} sx={{ minWidth: { xs: 180, sm: 220, md: 240 }, cursor: 'pointer', '& .MuiOutlinedInput-root': { borderRadius: '10px', bgcolor: '#ffffff', fontWeight: 600, color: '#0f172a', paddingRight: '8px', cursor: 'pointer', transition: 'border-color 0.15s, box-shadow 0.15s', '& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 }, '&:hover fieldset': { borderColor: '#cbd5e1' }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#C01227')}` }, '&.Mui-focused fieldset': { borderColor: '#C01227', borderWidth: 1.5 } }, '& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: '#94a3b8' } }} /> )} /> ); })()} {/* Date range pill — opens the date picker. Stays visible only when a date range is set. Uses the same pill aesthetic as Batch. */} {startdate && enddate && ( setOpen(true)} sx={{ pl: 0.5, pr: 1.25, py: 0.5, borderRadius: '10px', cursor: 'pointer', bgcolor: '#ffffff', border: '1px solid #e2e8f0', transition: 'border-color 0.15s', '&:hover': { borderColor: '#cbd5e1' } }} > {dayjs(startdate).format('DD MMM')} – {dayjs(enddate).format('DD MMM')} )}
    {/* Tenant */} { if (!appId) { event.preventDefault(); OpenToast('Please select your zone first!', 'warning', 3000); setTimeout(() => locationRef.current?.focus(), 0); } }} onChange={(e, val, reason) => { if (reason === 'clear') { setTenantid(0); setTenantValue(null); setLocationid(0); setLocationValue(null); } else { setTenantid(val?.tenantid || 0); setTenantValue(val); setLocationid(0); setLocationValue(null); } }} renderInput={(params) => ( ) }} sx={pillFieldSx('#0ea5e9')} /> )} /> {/* Location */} `${option.locationname} (${option.suburb})` || ''} value={locationValue} PaperComponent={SoftPaper} sx={{ flex: { xs: '1 1 100%', sm: '1 1 180px' }, minWidth: { xs: '100%', sm: 180 } }} onOpen={(event) => { if (!appId && !tenantid) { event.preventDefault(); OpenToast('Please select your Zone and Tenant first!', 'warning', 3000); setTimeout(() => locationRef.current?.focus(), 0); } else if (!tenantid) { event.preventDefault(); OpenToast('Please select your Tenant first!', 'warning', 3000); setTimeout(() => tenantRef.current?.focus(), 0); } }} onChange={(e, val, reason) => { if (reason === 'clear') { setLocationid(0); setLocationValue(null); } else { setLocationid(val.locationid || 0); setLocationValue(val); } }} renderInput={(params) => ( ) }} sx={pillFieldSx('#14b8a6')} /> )} /> {/* Rider */} `${option.firstname} ${option.lastname} (${option.contactno})`} sx={{ flex: { xs: '1 1 100%', sm: '1 1 180px' }, minWidth: { xs: '100%', sm: 180 } }} onChange={(e, value, reason) => { if (reason === 'clear') setRiderid(0); else setRiderid(value.userid); }} onOpen={(event) => { if (!appId) { event.preventDefault(); OpenToast('Please select your zone first!', 'warning', 3000); setTimeout(() => locationRef.current?.focus(), 0); } }} renderInput={(params) => ( ) }} sx={pillFieldSx('#8b5cf6')} /> )} />
    {/* ============================================= || Status Tabs + Search || ============================================= */} {STATUS_TABS.map((t, idx) => { const meta = STATUS_META[t.status]; const Icon = meta.icon; const active = tabvalue === idx; const count = batchCounts[t.countKey] ?? 0; return ( handleChangetab(e, idx)} sx={{ display: 'inline-flex', alignItems: 'center', gap: { xs: 0.625, md: 0.875 }, pl: 0.5, pr: { xs: 1, md: 1.25 }, py: 0.5, flexShrink: 0, cursor: 'pointer', borderRadius: DT.radiusField + 'px', border: `1px solid ${active ? meta.color : DT.borderSubtle}`, bgcolor: active ? meta.color : DT.surface, color: active ? '#fff' : DT.textSecondary, fontWeight: 600, boxShadow: 'none', transition: 'background-color 0.15s, border-color 0.15s, color 0.15s', '&:hover': { borderColor: active ? meta.color : DT.borderHover, bgcolor: active ? meta.color : DT.surfaceAlt } }} > {meta.label} {count} ); })} {/* ============================================= || Search | ============================================= */} {/* ============================================= || Table || ============================================= */} {(() => { const showAction = tabstatus !== 'Cancelled' && tabstatus !== 'Delivered'; const showSelect = tabstatus == 'Created'; const totalCols = 7 + (showAction ? 1 : 0) + (showSelect ? 1 : 0); return isMobile ? ( /* ===================== MOBILE: card list ===================== */ {filteredRows.length === 0 && !loading1 && !countSourceLoading && ( No deliveries to show {selectedBatch === 'all' ? `No ${(STATUS_META[currentStatus]?.label || tabstatus).toLowerCase()} orders for this filter.` : `No ${(STATUS_META[currentStatus]?.label || tabstatus).toLowerCase()} orders in ${BATCH_OPTIONS.find((b) => b.id === selectedBatch)?.label || 'this batch'}.`} )} {filteredRows.length === 0 && countSourceLoading && ( Loading deliveries… )} {filteredRows.map((row, index) => { const rowStatusMeta = STATUS_META[String(row.status || '').toLowerCase()] || { label: row.status || '—', color: '#94a3b8', icon: MdHistoryToggleOff }; const RowStatusIcon = rowStatusMeta.icon; const isSelected = !!deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid); const isOpen = productCollapse?.orderid === row?.orderid; return ( {showSelect && ( { if (e.target.checked) { let arr = deliverylist; arr.push({ ...row, sno: deliverylist.length + 1 }); setDeliverylist([...arr]); } else { let res = deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid); if (res) { let arr = deliverylist; arr.splice(res.sno - 1, 1); arr.map((val, i) => { val.sno = i + 1; }); setDeliverylist([...arr]); } } }} checked={!!deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid)} /> )} {String(page * rowsPerPage + index + 1).padStart(2, '0')} {rowStatusMeta.label} {row.deliverytype == 'C' && ( { if (productCollapse?.orderid === row.orderid) { setProductCollapse(null); setOrderHeaderId(null); } else { setProductCollapse(row); setOrderHeaderId(row.orderheaderid); } }} sx={{ borderRadius: 999, bgcolor: tint('#06b6d4'), color: '#06b6d4', border: `1px solid ${edge('#06b6d4')}`, '&:hover': { bgcolor: soft('#06b6d4') } }} > {isOpen ? : } )} {showAction && ( handleMenuOpen(e, row)} sx={{ borderRadius: 999, bgcolor: tint('#C01227'), color: '#C01227', border: `1px solid ${edge('#C01227')}`, '&:hover': { bgcolor: soft('#C01227') } }} > )} {row.bookingreference || `#${row.bookingid}`} {row.createdat ? dayjs(row.createdat).format('DD/MM/YYYY hh:mm A') : '—'} } > {row.pickupaddress || '—'} {row.deliveryaddress || '—'} {row.assignedmileruserid ? ( #{row.assignedmileruserid} ) : ( Unassigned )} {isOpen && ( Product Details {orderdetails?.details?.map((product, idx2) => ( {product?.productname || 'Unnamed'} Qty {product?.orderqty || 0} · ₹ {product?.price || 0} ₹ {(product?.productsumprice + product?.taxamount).toFixed(2) || 0} ))} Total Amount ₹ {orderdetails?.pricedetails?.orderamount?.toFixed(2)} )} ); })} {countSourceRows?.length != 0 && (
    {countIsFetchingNext || countHasNext ? ( ) : ( · End of list · )}
    )}
    ) : ( {showSelect && ( 0 && deliverylist.length != filteredRows.length} onChange={(e) => { if (e.target.checked) { setDeliverylist([...filteredRows]); } else { setDeliverylist([]); } }} checked={deliverylist.length > 0 && deliverylist.length == filteredRows.length} /> )} # Status Booking Pickup Delivery Miler Created {showAction && Action} {(loading1 || countSourceIsLoading) && } {filteredRows.length == 0 && !loading1 && !countSourceLoading && ( No deliveries to show {selectedBatch === 'all' ? `No ${(STATUS_META[currentStatus]?.label || tabstatus).toLowerCase()} orders for this filter.` : `No ${(STATUS_META[currentStatus]?.label || tabstatus).toLowerCase()} orders in ${BATCH_OPTIONS.find((b) => b.id === selectedBatch)?.label || 'this batch'}.`} )} {filteredRows.length == 0 && countSourceLoading && ( Loading deliveries… )} {filteredRows.map((row, index) => { const rowStatusMeta = STATUS_META[String(row.status || '').toLowerCase()] || { label: row.status || '—', color: '#94a3b8', icon: MdHistoryToggleOff }; const RowStatusIcon = rowStatusMeta.icon; const isSelected = !!deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid); return ( {tabstatus == 'Created' && ( { if (e.target.checked) { let arr = deliverylist; arr.push({ ...row, sno: deliverylist.length + 1 }); setDeliverylist([...arr]); } else { let res = deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid); if (res) { let arr = deliverylist; let res = deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid); arr.splice(res.sno - 1, 1); arr.map((val, i) => { val.sno = i + 1; }); setDeliverylist([...arr]); } } console.log('list', deliverylist); }} checked={deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid)} /> )} {String(page * rowsPerPage + index + 1).padStart(2, '0')} {/* Status badge */} {rowStatusMeta.label} {/* Booking */} {row.bookingreference || `#${row.bookingid}`} {/* Pickup */} {row.pickupaddress || '—'} {/* Delivery */} {row.deliveryaddress || '—'} {/* Miler */} {row.assignedmileruserid ? ( #{row.assignedmileruserid} ) : ( Unassigned )} {/* Created */} {row.createdat ? dayjs(row.createdat).format('DD/MM/YYYY hh:mm A') : '—'} {/* Action */} {tabstatus !== 'Cancelled' && tabstatus !== 'Delivered' && ( {row.deliverytype == 'C' && ( { if (productCollapse?.orderid === row.orderid) { setProductCollapse(null); setOrderHeaderId(null); } else { setProductCollapse(row); setOrderHeaderId(row.orderheaderid); } }} sx={{ borderRadius: 999, bgcolor: tint('#06b6d4'), color: '#06b6d4', border: `1px solid ${edge('#06b6d4')}`, '&:hover': { bgcolor: soft('#06b6d4') } }} > {productCollapse?.orderid === row.orderid ? : } )} handleMenuOpen(e, row)} sx={{ borderRadius: 999, bgcolor: tint('#C01227'), color: '#C01227', border: `1px solid ${edge('#C01227')}`, '&:hover': { bgcolor: soft('#C01227') } }} > )} {productCollapse?.orderid === row?.orderid && ( Product Details
    # Product Description Qty Cost Price Tax Amount {orderdetails?.details?.map((product, idx2) => ( {String(idx2 + 1).padStart(2, '0')} {product?.productname || 'Unnamed'} {product?.productdescription || '-'} {product?.orderqty || 0} ₹ {product?.price || 0} ₹ {(product?.productsumprice ?? 0).toFixed(2)} ₹ {(product?.taxamount ?? 0).toFixed(2)} ₹ {(product?.productsumprice + product?.taxamount).toFixed(2) || 0} ))} Total Amount ₹ {orderdetails?.pricedetails?.orderamount?.toFixed(2)}
    )} ); })} {countSourceRows?.length != 0 && (
    {countIsFetchingNext ? : countHasNext ? : ( · End of list · )}
    )} ); })()}
    {/* Shared row-action menu — single instance reused by both the desktop table rows and the mobile cards (both trigger handleMenuOpen). */} {String(selectedRow?.status || '').toLowerCase() !== 'delivered' && ( { notifyRiderMutation.mutate(selectedRow.userfcmtoken); handleMenuClose(); }} > Notify Rider )} {['pending_pickup', 'miler_assigned', 'pickup_scheduled', 'at_customer'].includes( String(selectedRow?.status || '').toLowerCase() ) && ( { if (!appId) { opentoast('Please select a location first!', 'warning'); locationRef.current?.focus(); return; } setChangeDialogOpen(true); handleMenuClose(); }} > Change Rider )} {(roleid == 1 || roleid == 2) && ( { setKms(selectedRow.kms); setCumulativeKms(selectedRow.cumulativekms); setDeliverylat(selectedRow.droplat); setDeliverylong(selectedRow.droplon); setNotes(selectedRow.notes); setDeliveryamount(selectedRow.deliveryamount); setUpdateStatus(selectedRow.status || 'delivered'); setCurrentorder(selectedRow); setDialogopen(true); handleMenuClose(); }} > Update Status )} {!['cancelled', 'delivered'].includes(String(selectedRow?.status || '').toLowerCase()) && ( { setCancelDeliveryOpen(true); handleMenuClose(); }} > Cancel Delivery )} {/* =============================== || cancel dialog || =============================== */} setCancelDeliveryOpen(false)} maxWidth="xs" fullWidth PaperProps={{ elevation: 0, sx: { borderRadius: 3, border: '1px solid', borderColor: DT.borderSubtle, boxShadow: DT.shadowPop, overflow: 'hidden' } }} > Cancel Delivery? This action will cancel the delivery and notify the rider. Please share a brief reason. setCancelFeed(e.target.value)} sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2, bgcolor: DT.surfaceAlt, '& fieldset': { borderColor: DT.borderSubtle }, '&:hover fieldset': { borderColor: '#ef4444' }, '&.Mui-focused fieldset': { borderColor: '#ef4444', borderWidth: 2 }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#ef4444')}` } } }} /> {/* =============================== || change rider dialog || =============================== */} setChangeDialogOpen(false)} maxWidth="sm" fullWidth TransitionComponent={PopupTransition} PaperProps={{ elevation: 0, sx: { borderRadius: 3, border: '1px solid', borderColor: DT.borderSubtle, boxShadow: DT.shadowPop, overflow: 'hidden' } }} > Change Rider Assign this delivery to a different rider `${option.firstname} ${option.lastname} (${option.contactno})`} onChange={(e, value) => { setSelectedRider(value); logger.debug('Rider selected in dropdown:', value ? `${value.firstname} ${value.lastname}` : 'None'); }} renderInput={(params) => ( ) }} sx={pillFieldSx('#8b5cf6')} /> )} /> {/* =============================== || Date filter Dialog || =============================== */} setOpen(false)} fullScreen={isMobile} PaperProps={{ elevation: 0, sx: { borderRadius: { xs: 0, sm: 3 }, border: '1px solid', borderColor: DT.borderSubtle, boxShadow: DT.shadowPop, overflow: 'hidden' } }} > Date Range Pick a window to filter the deliveries setOpen(!open)} id="daterange1" onChange={(range) => { if (range.label === 'All') { setStartdate(''); setEnddate(''); setOpen(false); } else { setStartdate(dayjs(range.startDate).format('YYYY-MM-DD')); setEnddate(dayjs(range.endDate).format('YYYY-MM-DD')); } console.log(range); }} definedRanges={[ { label: 'Today', startDate: new Date(), endDate: new Date() }, { label: 'Yesterday', startDate: addDays(new Date(), -1), endDate: addDays(new Date(), -1) }, { label: 'Tomorrow', startDate: addDays(new Date(), +1), endDate: addDays(new Date(), +1) }, { label: 'This Week', startDate: startOfWeek(new Date()), endDate: endOfWeek(new Date()) }, { label: 'Last Week', startDate: startOfWeek(addWeeks(new Date(), -1)), endDate: endOfWeek(addWeeks(new Date(), -1)) }, { label: 'Last 7 Days', startDate: addWeeks(new Date(), -1), endDate: new Date() }, { label: 'This Month', startDate: startOfMonth(new Date()), endDate: endOfMonth(new Date()) }, { label: 'Last Month', startDate: startOfMonth(addMonths(new Date(), -1)), endDate: endOfMonth(addMonths(new Date(), -1)) } ]} /> {/* =============================== || Update Delivery Dialog || =============================== */} Update Delivery Status {currentorder?.orderid ? `Order #${currentorder.orderid}` : 'Modify delivery details'} {[ { label: 'KMs', value: kms, set: (v) => setKms(v), color: '#ef4444' }, { label: 'Actual KMs', value: cumulativekms, set: (v) => setCumulativeKms(+v), color: '#10b981' }, { label: 'Delivery Latitude', value: deliverylat, set: (v) => setDeliverylat(v), color: '#06b6d4' }, { label: 'Delivery Longitude', value: deliverylong, set: (v) => setDeliverylong(v), color: '#06b6d4' } ].map((f) => ( {f.label} f.set(e.target.value)} sx={{ mt: 0.5, '& .MuiOutlinedInput-root': { borderRadius: 2, bgcolor: '#fff', '& fieldset': { borderColor: DT.borderSubtle }, '&:hover fieldset': { borderColor: f.color }, '&.Mui-focused fieldset': { borderColor: f.color, borderWidth: 2 }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(f.color)}` } } }} /> ))} Amount setDeliveryamount(+e.target.value)} sx={{ mt: 0.5, '& .MuiOutlinedInput-root': { borderRadius: 2, bgcolor: '#fff', '& fieldset': { borderColor: DT.borderSubtle }, '&:hover fieldset': { borderColor: '#10b981' }, '&.Mui-focused fieldset': { borderColor: '#10b981', borderWidth: 2 }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#10b981')}` } } }} /> Status setUpdateStatus(e.target.value)} SelectProps={{ MenuProps: { PaperProps: { sx: { borderRadius: 2, mt: 0.5, boxShadow: DT.shadowPop } } } }} sx={{ mt: 0.5, '& .MuiOutlinedInput-root': { borderRadius: 2, bgcolor: '#fff', '& fieldset': { borderColor: DT.borderSubtle }, '&:hover fieldset': { borderColor: '#C01227' }, '&.Mui-focused fieldset': { borderColor: '#C01227', borderWidth: 2 }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#C01227')}` } } }} > {['pending','accepted','started','arrived','delivered','cancelled'].map((s) => { const m = STATUS_META[s] || { label: s, color: '#C01227', icon: MdHistoryToggleOff }; const Ic = m.icon; return ( {m.label} ); })} Notes setNotes(e.target.value)} sx={{ mt: 0.5, '& .MuiOutlinedInput-root': { borderRadius: 2, bgcolor: '#fff', '& fieldset': { borderColor: DT.borderSubtle }, '&:hover fieldset': { borderColor: '#C01227' }, '&.Mui-focused fieldset': { borderColor: '#C01227', borderWidth: 2 }, '&.Mui-focused': { boxShadow: `0 0 0 3px ${ring('#C01227')}` } } }} /> ); }; export default Deliveries;