2174 lines
86 KiB
JavaScript
2174 lines
86 KiB
JavaScript
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 { Empty } from 'antd';
|
||
import dayjs from 'dayjs';
|
||
var utc = require('dayjs/plugin/utc');
|
||
dayjs.extend(utc);
|
||
import axios from 'axios';
|
||
import HoverSocialCard from 'components/cards/statistics/HoverSocialCard';
|
||
import { useTheme } from '@mui/material/styles';
|
||
import {
|
||
MdOutlineDateRange,
|
||
MdAccessTime,
|
||
MdAllInclusive,
|
||
MdLightMode,
|
||
MdWbSunny,
|
||
MdNightsStay,
|
||
MdCheck
|
||
} from 'react-icons/md';
|
||
import { useQuery, useMutation, useInfiniteQuery } from '@tanstack/react-query';
|
||
|
||
import {
|
||
Avatar,
|
||
Box,
|
||
Button,
|
||
Grid,
|
||
Tabs,
|
||
Tab,
|
||
IconButton,
|
||
Stack,
|
||
Chip,
|
||
Typography,
|
||
Table,
|
||
TableCell,
|
||
TableBody,
|
||
TableHead,
|
||
Collapse,
|
||
Dialog,
|
||
TableRow,
|
||
DialogContent,
|
||
DialogTitle,
|
||
Tooltip,
|
||
DialogActions,
|
||
Popper,
|
||
ClickAwayListener,
|
||
Checkbox,
|
||
Autocomplete,
|
||
TextField,
|
||
FormLabel,
|
||
TablePagination,
|
||
TableContainer,
|
||
Skeleton,
|
||
useMediaQuery,
|
||
Divider,
|
||
CircularProgress,
|
||
Backdrop,
|
||
MenuItem,
|
||
Menu,
|
||
Paper
|
||
} from '@mui/material';
|
||
|
||
import MainCard from 'components/MainCard';
|
||
import {
|
||
MoreOutlined,
|
||
CloseOutlined
|
||
// NotificationOutlined
|
||
} from '@ant-design/icons';
|
||
import { PopupTransition } from 'components/@extended/Transitions';
|
||
import {
|
||
addDays,
|
||
addMonths,
|
||
addWeeks,
|
||
// addYears,
|
||
endOfMonth,
|
||
endOfWeek,
|
||
// endOfYear,
|
||
startOfMonth,
|
||
startOfWeek
|
||
// startOfYear,
|
||
} 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 CircularLoader from 'components/CircularLoader';
|
||
|
||
import {
|
||
cancelDeliveryAPI,
|
||
changeRiderAPI,
|
||
fetchCountAPI,
|
||
fetchDeliveries,
|
||
fetchPercentageAPI,
|
||
fetchRidersList,
|
||
notifyRider,
|
||
updateDeliveryAPI,
|
||
getorderdetails,
|
||
gettenantlocations,
|
||
getTenants
|
||
} from 'pages/api/api';
|
||
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
|
||
import TitleCard from 'components/nearle_components/TitleCard';
|
||
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';
|
||
|
||
// 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 PM', color: '#f59e0b', iconKey: 'afternoon', startHour: 9, endHour: 12 },
|
||
{ 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;
|
||
};
|
||
|
||
// ================================================= || deliveries (initial point)|| =================================================
|
||
const Deliveries = () => {
|
||
const userid = localStorage.getItem('userid');
|
||
const theme = useTheme();
|
||
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('Pending');
|
||
const [tabvalue, setTabvalue] = useState(0);
|
||
const [open, setOpen] = useState(false);
|
||
const [datestatus, setDatestatus] = useState('Today');
|
||
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('pending');
|
||
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 [totalCount, setTotalCount] = React.useState();
|
||
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 [anchorEl, setAnchorEl] = React.useState(null);
|
||
const [open2, setOpen2] = useState('');
|
||
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, { selectedRider, 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);
|
||
if (i === 0) {
|
||
setTabstatus('Pending');
|
||
setCurrentStatus('pending');
|
||
setTotalCount(countData?.uncoveredLength);
|
||
}
|
||
if (i === 1) {
|
||
setTabstatus('Assigned');
|
||
setCurrentStatus('accepted');
|
||
setTotalCount(countData?.assignedLength);
|
||
}
|
||
if (i === 2) {
|
||
setTabstatus('Arrived');
|
||
setCurrentStatus('arrived');
|
||
setTotalCount(countData?.arrivedLength);
|
||
}
|
||
if (i === 3) {
|
||
setTabstatus('Picked');
|
||
setCurrentStatus('picked');
|
||
setTotalCount(countData?.pickedLength);
|
||
}
|
||
if (i === 4) {
|
||
setTabstatus('Active');
|
||
setCurrentStatus('active');
|
||
setTotalCount(countData?.activeLength);
|
||
}
|
||
if (i === 5) {
|
||
setTabstatus('Skipped');
|
||
setCurrentStatus('skipped');
|
||
setTotalCount(countData?.skippedLength);
|
||
}
|
||
if (i === 6) {
|
||
setTabstatus('Delivered');
|
||
setCurrentStatus('delivered');
|
||
setTotalCount(countData?.coveredLength);
|
||
}
|
||
|
||
if (i === 7) {
|
||
setTabstatus('Cancelled');
|
||
setCurrentStatus('cancelled');
|
||
setTotalCount(countData?.cancelLength);
|
||
}
|
||
console.log(i);
|
||
setSearchword('');
|
||
};
|
||
|
||
const okclicked = () => {
|
||
setOpen(false);
|
||
};
|
||
|
||
/* ============================================= || fetchDeliveries | ============================================= */
|
||
|
||
const {
|
||
data: deliveriesData,
|
||
isLoading: fetchDeliveriesIsLoading,
|
||
isError: fetchDeliveriesIsError,
|
||
error: fetchDeliveriesError,
|
||
fetchNextPage,
|
||
hasNextPage,
|
||
isFetchingNextPage,
|
||
refetch: fetchDeliveriesRefetch
|
||
} = useInfiniteQuery({
|
||
queryKey: [
|
||
'fetchdeliveries',
|
||
appId,
|
||
userid,
|
||
currentStatus,
|
||
startdate,
|
||
enddate,
|
||
rowsPerPage,
|
||
debouncedSearch,
|
||
tenantid,
|
||
locationid,
|
||
riderid
|
||
],
|
||
queryFn: fetchDeliveries,
|
||
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined
|
||
});
|
||
const rows = deliveriesData?.pages.flatMap((page) => page.rows) || [];
|
||
|
||
// (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 <status> 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.orderstatus || '').toLowerCase();
|
||
if (wantStatus && s !== wantStatus) return false;
|
||
if (q) {
|
||
const hay = [
|
||
r.deliverycustomer,
|
||
r.deliveryaddress,
|
||
r.deliverysuburb,
|
||
r.pickupcustomer,
|
||
r.pickupaddress,
|
||
r.pickupsuburb,
|
||
r.orderid,
|
||
r.tenantname,
|
||
r.ridername,
|
||
r.username
|
||
]
|
||
.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 = {
|
||
uncoveredLength: 0,
|
||
assignedLength: 0,
|
||
arrivedLength: 0,
|
||
pickedLength: 0,
|
||
activeLength: 0,
|
||
skippedLength: 0,
|
||
coveredLength: 0,
|
||
cancelLength: 0
|
||
};
|
||
countSourceRows.forEach((r) => {
|
||
if (selectedBatch !== 'all' && getRowBatchId(r) !== selectedBatch) return;
|
||
const s = String(r.orderstatus || '').toLowerCase();
|
||
switch (s) {
|
||
case 'pending':
|
||
c.uncoveredLength += 1;
|
||
break;
|
||
case 'accepted':
|
||
case 'assigned':
|
||
c.assignedLength += 1;
|
||
break;
|
||
case 'arrived':
|
||
c.arrivedLength += 1;
|
||
break;
|
||
case 'picked':
|
||
c.pickedLength += 1;
|
||
break;
|
||
case 'active':
|
||
c.activeLength += 1;
|
||
break;
|
||
case 'skipped':
|
||
c.skippedLength += 1;
|
||
break;
|
||
case 'delivered':
|
||
c.coveredLength += 1;
|
||
break;
|
||
case 'cancelled':
|
||
case 'canceled':
|
||
c.cancelLength += 1;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
});
|
||
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(() => {
|
||
const totals = { all: countSourceRows.length };
|
||
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;
|
||
});
|
||
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 | ============================================= */
|
||
|
||
const {
|
||
data: percentageData,
|
||
isLoading: fetchPercentageIsLoading,
|
||
isError: fetchPercentageIsError,
|
||
error: fetchPercentageError
|
||
} = useQuery({
|
||
queryKey: ['fetchpercentageaPI', appId],
|
||
queryFn: () => fetchPercentageAPI(appId)
|
||
});
|
||
useEffect(() => {
|
||
if (percentageData) {
|
||
console.log('percentageData', percentageData);
|
||
}
|
||
}, [percentageData]);
|
||
|
||
/* ============================================= || 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)
|
||
});
|
||
useEffect(() => {
|
||
console.log('countData', countData);
|
||
if (tabvalue === 0 && countData) {
|
||
setTotalCount(countData.uncoveredLength);
|
||
}
|
||
}, [countData]);
|
||
|
||
// ==============================|| 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 percentages: ${fetchDeliveriesError?.message}`
|
||
: fetchPercentageIsError
|
||
? `Error fetching percentages: ${fetchPercentageError?.message}`
|
||
: fetchCountIsError
|
||
? `Error fetching percentages: ${fetchCountError?.message}`
|
||
: ridersListIsError
|
||
? `Error fetching percentages: ${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 ||
|
||
fetchPercentageIsLoading ||
|
||
countSourceIsLoading ||
|
||
fetchtenantsIsLoading ||
|
||
fetchlocationsIsLoading ||
|
||
riderListIsLoading) && (
|
||
<>
|
||
<Loader />
|
||
{/* <CircularLoader /> */}
|
||
</>
|
||
)}
|
||
{
|
||
<Backdrop
|
||
sx={{
|
||
color: '#fff',
|
||
zIndex: (theme) => theme.zIndex.drawer + 1
|
||
}}
|
||
open={
|
||
fetchCountIsLoading ||
|
||
fetchPercentageIsLoading ||
|
||
fetchDeliveriesIsLoading ||
|
||
fetchtenantsIsLoading ||
|
||
fetchlocationsIsLoading ||
|
||
riderListIsLoading
|
||
}
|
||
>
|
||
{/* <CircularLoader color="inherit" /> */}
|
||
</Backdrop>
|
||
}
|
||
{/* ============================================= || TitleCard | ============================================= */}
|
||
<TitleCard title={'Deliveries'}>
|
||
<LocationAutocomplete
|
||
ref={locationRef}
|
||
locaName={locaName}
|
||
setAppId={setAppId}
|
||
setLocoName={setLocoName}
|
||
setPage={setPage}
|
||
sx={{ width: { xs: '100%', custom450: 300 }, zIndex: '100' }}
|
||
/>
|
||
</TitleCard>
|
||
|
||
{/* ============================================= || hoverCard | ============================================= */}
|
||
<Grid container spacing={2} sx={{ mt: '1px' }}>
|
||
{[
|
||
{
|
||
label: 'Created orders',
|
||
value: percentageData?.uncoveredOrders,
|
||
percentage: percentageData?.percentage1,
|
||
color: theme.palette.info.main
|
||
},
|
||
{
|
||
label: 'Pending orders',
|
||
value: percentageData?.assignedOrders,
|
||
percentage: percentageData?.percentage2,
|
||
color: theme.palette.warning.main
|
||
},
|
||
{
|
||
label: 'Delivered orders',
|
||
value: percentageData?.pickedOrders,
|
||
percentage: percentageData?.percentage3,
|
||
color: theme.palette.success.main
|
||
},
|
||
{
|
||
label: 'Cancelled Orders',
|
||
value: percentageData?.coveredOrders,
|
||
percentage: percentageData?.percentage4,
|
||
color: theme.palette.error.main
|
||
}
|
||
].map((item, index) => (
|
||
<Grid item key={index} xs={12} custom400={6} sm={3} sx={{ cursor: 'pointer' }}>
|
||
<HoverSocialCard
|
||
primary={item.label}
|
||
secondary={fetchPercentageIsLoading ? <Skeleton sx={{ width: '30px' }} animation="wave" /> : item.value}
|
||
percentage={item.percentage?.toString()}
|
||
color={item.color}
|
||
/>
|
||
</Grid>
|
||
))}
|
||
</Grid>
|
||
|
||
{/* ============================================= || orderFilter | ============================================= */}
|
||
|
||
<Stack direction="row" justifyContent="space-between" alignItems="center" gap={3} sx={{ flexWrap: 'wrap', my: 2 }}>
|
||
<Stack
|
||
direction="row"
|
||
flexWrap="wrap"
|
||
gap={1.5}
|
||
alignItems="center"
|
||
>
|
||
{/* 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 (
|
||
<Autocomplete
|
||
disableClearable
|
||
size="small"
|
||
// Click-only dropdown: typing is disabled (readOnly input below),
|
||
// but the field still opens the menu on click. openOnFocus +
|
||
// selectOnFocus give the operator one-click access; the search
|
||
// filter built into Autocomplete is suppressed since there's
|
||
// nothing to search across only 4 options.
|
||
openOnFocus
|
||
selectOnFocus
|
||
filterOptions={(opts) => 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={(paperProps) => (
|
||
<Paper
|
||
{...paperProps}
|
||
sx={{
|
||
mt: 0.75,
|
||
borderRadius: 2,
|
||
boxShadow: '0 14px 40px rgba(15, 23, 42, 0.18)',
|
||
border: '1px solid',
|
||
borderColor: 'divider',
|
||
overflow: 'hidden'
|
||
}}
|
||
/>
|
||
)}
|
||
ListboxProps={{ sx: { py: 0, maxHeight: 360 } }}
|
||
renderOption={(props, option, { selected }) => {
|
||
const Icon = BATCH_ICONS[option.iconKey] || MdAccessTime;
|
||
const total = batchTotals[option.id] ?? 0;
|
||
return (
|
||
<li
|
||
{...props}
|
||
key={option.id}
|
||
style={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: 12,
|
||
padding: '10px 14px',
|
||
borderBottom: '1px solid #f1f5f9',
|
||
backgroundColor: selected ? `${option.color}10` : 'transparent',
|
||
transition: 'background-color 0.15s'
|
||
}}
|
||
>
|
||
<Avatar
|
||
sx={{
|
||
width: 36,
|
||
height: 36,
|
||
bgcolor: selected ? option.color : `${option.color}18`,
|
||
color: selected ? '#fff' : option.color,
|
||
transition: 'background-color 0.15s, color 0.15s'
|
||
}}
|
||
>
|
||
<Icon size={20} />
|
||
</Avatar>
|
||
<Stack direction="column" spacing={0} flex={1} minWidth={0}>
|
||
<Typography
|
||
variant="body2"
|
||
fontWeight={700}
|
||
color="#0f172a"
|
||
noWrap
|
||
>
|
||
{option.label}
|
||
</Typography>
|
||
<Typography variant="caption" color="text.secondary" noWrap>
|
||
{option.range}
|
||
</Typography>
|
||
</Stack>
|
||
{/* 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. */}
|
||
<Chip
|
||
size="small"
|
||
label={total}
|
||
sx={{
|
||
minWidth: 36,
|
||
height: 22,
|
||
fontWeight: 800,
|
||
fontSize: 11,
|
||
bgcolor: total > 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 && (
|
||
<Avatar
|
||
sx={{
|
||
width: 22,
|
||
height: 22,
|
||
bgcolor: option.color,
|
||
color: '#fff'
|
||
}}
|
||
>
|
||
<MdCheck size={14} />
|
||
</Avatar>
|
||
)}
|
||
</li>
|
||
);
|
||
}}
|
||
renderInput={(params) => (
|
||
<TextField
|
||
{...params}
|
||
placeholder="Batch"
|
||
// readOnly disables typing; the input still receives focus
|
||
// and clicks, so the popup still opens. caretColor:transparent
|
||
// hides the blinking caret that focus would normally show.
|
||
inputProps={{
|
||
...params.inputProps,
|
||
readOnly: true,
|
||
style: { ...(params.inputProps?.style || {}), cursor: 'pointer', caretColor: 'transparent' }
|
||
}}
|
||
InputProps={{
|
||
...params.InputProps,
|
||
startAdornment: (
|
||
<Stack
|
||
direction="row"
|
||
alignItems="center"
|
||
spacing={0.75}
|
||
sx={{ pl: 0.5, mr: 0.25, flexShrink: 0 }}
|
||
>
|
||
<Avatar
|
||
sx={{
|
||
width: 24,
|
||
height: 24,
|
||
bgcolor: `${activeBatch.color}18`,
|
||
color: activeBatch.color
|
||
}}
|
||
>
|
||
<ActiveIcon size={14} />
|
||
</Avatar>
|
||
</Stack>
|
||
)
|
||
}}
|
||
sx={{
|
||
minWidth: 240,
|
||
cursor: 'pointer',
|
||
'& .MuiOutlinedInput-root': {
|
||
borderRadius: '999px',
|
||
bgcolor: `${activeBatch.color}08`,
|
||
fontWeight: 700,
|
||
color: activeBatch.color,
|
||
paddingRight: '8px',
|
||
cursor: 'pointer',
|
||
transition: 'border-color 0.15s, box-shadow 0.15s, background-color 0.2s',
|
||
'& fieldset': {
|
||
borderColor: `${activeBatch.color}55`,
|
||
borderWidth: 1.5
|
||
},
|
||
'&:hover fieldset': { borderColor: activeBatch.color },
|
||
'&.Mui-focused': {
|
||
boxShadow: `0 0 0 3px ${activeBatch.color}26`
|
||
},
|
||
'&.Mui-focused fieldset': {
|
||
borderColor: activeBatch.color,
|
||
borderWidth: 2
|
||
}
|
||
},
|
||
'& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': {
|
||
color: activeBatch.color
|
||
}
|
||
}}
|
||
/>
|
||
)}
|
||
/>
|
||
);
|
||
})()}
|
||
|
||
{/* Date range chip — opens the date picker. Stays visible only when
|
||
a date range is set (matches prior behavior). */}
|
||
{startdate && enddate && (
|
||
<Chip
|
||
avatar={
|
||
<Avatar sx={{ backgroundColor: 'transparent' }}>
|
||
<MdOutlineDateRange fontSize="30px" style={{ color: '#fbc02d' }} />
|
||
</Avatar>
|
||
}
|
||
label={
|
||
<Typography noWrap color="secondary">
|
||
{dayjs(startdate).format('DD/MM/YYYY')} - {dayjs(enddate).format('DD/MM/YYYY')}
|
||
</Typography>
|
||
}
|
||
onClick={() => setOpen(true)}
|
||
variant="combined"
|
||
color="warning"
|
||
sx={{ maxWidth: '100%', cursor: 'pointer' }}
|
||
/>
|
||
)}
|
||
|
||
</Stack>
|
||
|
||
<Autocomplete
|
||
disablePortal
|
||
options={tenantlist || []}
|
||
value={tenantValue}
|
||
sx={{ minWidth: 200, flex: 1 }}
|
||
onOpen={(event) => {
|
||
if (!appId) {
|
||
event.preventDefault();
|
||
OpenToast('Please select a your location 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) => <TextField {...params} inputRef={tenantRef} label="Select Tenant" />}
|
||
/>
|
||
<Autocomplete
|
||
disablePortal
|
||
options={locationlist || []}
|
||
getOptionLabel={(option) => `${option.locationname} (${option.suburb})` || ''}
|
||
value={locationValue}
|
||
sx={{ minWidth: 200, flex: 1 }}
|
||
onOpen={(event) => {
|
||
if (!appId && !tenantid) {
|
||
event.preventDefault();
|
||
OpenToast('Please select a your Location and Tenant first!', 'warning', 3000);
|
||
setTimeout(() => {
|
||
locationRef.current?.focus();
|
||
}, 0);
|
||
} else if (!tenantid) {
|
||
event.preventDefault();
|
||
OpenToast('Please select a 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) => <TextField {...params} label="Select Location" />}
|
||
/>
|
||
<Autocomplete
|
||
sx={{ minWidth: 200, flex: 1 }}
|
||
fullWidth
|
||
disabled={riderListIsLoading}
|
||
options={ridersList}
|
||
getOptionLabel={(option) => `${option.firstname} ${option.lastname} (${option.contactno})`}
|
||
renderInput={(params) => <TextField {...params} label="Choose Rider" />}
|
||
onChange={(e, value, reason) => {
|
||
if (reason === 'clear') {
|
||
setRiderid(0);
|
||
} else {
|
||
setRiderid(value.userid);
|
||
console.log('selected rider', value);
|
||
}
|
||
}}
|
||
onOpen={(event) => {
|
||
if (!appId) {
|
||
event.preventDefault();
|
||
OpenToast('Please select a your location first!', 'warning', 3000);
|
||
setTimeout(() => {
|
||
locationRef.current?.focus();
|
||
}, 0);
|
||
}
|
||
}}
|
||
/>
|
||
</Stack>
|
||
{/* ============================================= || Tabs || ============================================= */}
|
||
<Stack
|
||
display={'flex'}
|
||
flexDirection={'row'}
|
||
justifyContent={'space-between'}
|
||
alignItems={'center'}
|
||
flexWrap={'wrap-reverse'}
|
||
gap={2}
|
||
sx={{
|
||
border: '1px solid ',
|
||
borderBottom: 0,
|
||
borderColor: 'bg.main',
|
||
p: 1.5
|
||
}}
|
||
>
|
||
<Tabs
|
||
value={tabvalue}
|
||
onChange={handleChangetab}
|
||
variant="scrollable"
|
||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||
scrollButtons={useMediaQuery(theme.breakpoints.down('custom1000')) ? true : false}
|
||
allowScrollButtonsMobile
|
||
>
|
||
<Tab
|
||
label="Assigned"
|
||
icon={
|
||
<Chip
|
||
label={batchCounts.uncoveredLength}
|
||
color="primary"
|
||
variant="light"
|
||
size="small"
|
||
sx={{ minWidth: 40, justifyContent: 'center' }}
|
||
/>
|
||
}
|
||
iconPosition="end"
|
||
/>
|
||
<Tab
|
||
label="Accepted"
|
||
icon={
|
||
<Chip
|
||
label={batchCounts.assignedLength}
|
||
color="primary"
|
||
variant="light"
|
||
size="small"
|
||
sx={{ minWidth: 40, justifyContent: 'center' }}
|
||
/>
|
||
}
|
||
iconPosition="end"
|
||
/>
|
||
<Tab
|
||
label="Arrived"
|
||
icon={
|
||
<Chip
|
||
label={batchCounts.arrivedLength}
|
||
color="primary"
|
||
variant="light"
|
||
size="small"
|
||
sx={{ minWidth: 40, justifyContent: 'center' }}
|
||
/>
|
||
}
|
||
iconPosition="end"
|
||
/>
|
||
|
||
<Tab
|
||
label="Picked"
|
||
icon={
|
||
<Chip
|
||
label={batchCounts.pickedLength}
|
||
color="primary"
|
||
variant="light"
|
||
size="small"
|
||
sx={{ minWidth: 40, justifyContent: 'center' }}
|
||
/>
|
||
}
|
||
iconPosition="end"
|
||
/>
|
||
<Tab
|
||
label="Active"
|
||
icon={
|
||
<Chip
|
||
label={batchCounts.activeLength}
|
||
color="primary"
|
||
variant="light"
|
||
size="small"
|
||
sx={{ minWidth: 40, justifyContent: 'center' }}
|
||
/>
|
||
}
|
||
iconPosition="end"
|
||
/>
|
||
<Tab
|
||
label="Skipped"
|
||
icon={
|
||
<Chip
|
||
label={batchCounts.skippedLength}
|
||
color="primary"
|
||
variant="light"
|
||
size="small"
|
||
sx={{ minWidth: 40, justifyContent: 'center' }}
|
||
/>
|
||
}
|
||
iconPosition="end"
|
||
/>
|
||
|
||
<Tab
|
||
label="Delivered"
|
||
icon={
|
||
<Chip
|
||
label={batchCounts.coveredLength}
|
||
color="primary"
|
||
variant="light"
|
||
size="small"
|
||
sx={{ minWidth: 40, justifyContent: 'center' }}
|
||
/>
|
||
}
|
||
iconPosition="end"
|
||
/>
|
||
|
||
<Tab
|
||
label="Cancelled"
|
||
icon={
|
||
<Chip
|
||
label={batchCounts.cancelLength}
|
||
color="primary"
|
||
variant="light"
|
||
size="small"
|
||
sx={{ minWidth: 40, justifyContent: 'center' }}
|
||
/>
|
||
}
|
||
iconPosition="end"
|
||
/>
|
||
</Tabs>
|
||
|
||
{/* ============================================= || searchOutlined | ============================================= */}
|
||
<DebounceSearchBar
|
||
value={searchword}
|
||
onChange={setSearchword}
|
||
onDebouncedChange={setDebouncedSearch}
|
||
sx={{ m: 0, width: 280, flex: 1 }}
|
||
/>
|
||
</Stack>
|
||
{/* ============================================= || MainCard || ============================================= */}
|
||
<MainCard content={false}>
|
||
<TableContainer
|
||
ref={containerRef}
|
||
onScroll={handleScroll}
|
||
sx={{
|
||
maxHeight: 'calc(100vh - 190px)',
|
||
overflow: 'auto',
|
||
overflowX: 'auto',
|
||
'&::-webkit-scrollbar': {
|
||
width: '12px', // scroll bar width
|
||
cursor: 'pointer'
|
||
},
|
||
'&::-webkit-scrollbar-thumb': {
|
||
backgroundColor: theme.palette.primary.main, // thumb color
|
||
borderRadius: '8px',
|
||
cursor: 'pointer'
|
||
},
|
||
'&::-webkit-scrollbar-thumb:hover': {
|
||
backgroundColor: theme.palette.primary.dark, // hover color
|
||
cursor: 'pointer'
|
||
},
|
||
'&::-webkit-scrollbar-track': {
|
||
backgroundColor: theme.palette.primary.lighter,
|
||
cursor: 'pointer'
|
||
}
|
||
}}
|
||
>
|
||
<Table stickyHeader sx={{ minWidth: 1400 }}>
|
||
<TableHead>
|
||
<TableRow>
|
||
{tabstatus == 'Created' && (
|
||
<TableCell sx={{ whiteSpace: 'nowrap' }}>
|
||
<Checkbox
|
||
indeterminate={deliverylist.length > 0 && deliverylist.length != filteredRows.length}
|
||
onChange={(e) => {
|
||
if (e.target.checked) {
|
||
setDeliverylist([...filteredRows]);
|
||
} else {
|
||
setDeliverylist([]);
|
||
}
|
||
}}
|
||
checked={deliverylist.length > 0 && deliverylist.length == filteredRows.length}
|
||
/>
|
||
</TableCell>
|
||
)}
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>S.No </TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Tenant </TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>
|
||
Order Location{' '}
|
||
</TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Pickup </TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Drop </TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Rider </TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Est. Delivery Time</TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Transit</TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}> Kms</TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Amount </TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Notes </TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Steps</TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>Qty</TableCell>
|
||
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light, whiteSpace: 'nowrap', border: 'none' }}>COD </TableCell>
|
||
{tabstatus !== 'Cancelled' && tabstatus !== 'Delivered' && (
|
||
<TableCell
|
||
align="right"
|
||
sx={{
|
||
position: 'sticky !important',
|
||
backgroundColor: theme.palette.secondary.light,
|
||
whiteSpace: 'nowrap',
|
||
border: 'none'
|
||
}}
|
||
>
|
||
Action
|
||
</TableCell>
|
||
)}
|
||
</TableRow>
|
||
</TableHead>
|
||
{(loading1 || countSourceIsLoading) && <OrdersTableSkeleton col={8} />}
|
||
<TableBody>
|
||
{filteredRows.length == 0 && !loading1 && !countSourceLoading && (
|
||
<>
|
||
<TableCell colSpan={14}>
|
||
<Empty
|
||
description={
|
||
selectedBatch === 'all'
|
||
? `No ${tabstatus} Orders`
|
||
: `No ${tabstatus} Orders in ${BATCH_OPTIONS.find((b) => b.id === selectedBatch)?.label || 'this batch'}`
|
||
}
|
||
styles={{ description: { color: theme.palette.error.main } }}
|
||
/>
|
||
</TableCell>
|
||
</>
|
||
)}
|
||
{filteredRows.length == 0 && countSourceLoading && (
|
||
<TableRow>
|
||
<TableCell colSpan={14} sx={{ textAlign: 'center', py: 4 }}>
|
||
<LoaderWithImage />
|
||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1 }}>
|
||
Loading deliveries…
|
||
</Typography>
|
||
</TableCell>
|
||
</TableRow>
|
||
)}
|
||
{filteredRows.map((row, index) => {
|
||
return (
|
||
<>
|
||
<TableRow
|
||
role="checkbox"
|
||
tabIndex={-1}
|
||
key={row.tenantname}
|
||
sx={{
|
||
cursor: 'pointer',
|
||
backgroundColor: deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid) ? '#e1bee7' : '',
|
||
'&:hover': {
|
||
backgroundColor: deliverylist.find((res1) => res1.orderheaderid == row.orderheaderid) ? '#e1bee7 !important' : ''
|
||
}
|
||
}}
|
||
>
|
||
{tabstatus == 'Created' && (
|
||
<TableCell>
|
||
<Checkbox
|
||
onChange={(e) => {
|
||
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)}
|
||
/>
|
||
</TableCell>
|
||
)}
|
||
<TableCell>
|
||
{/* <Typography> {row.sno}</Typography> */}
|
||
<Typography> {page * rowsPerPage + index + 1}</Typography>
|
||
</TableCell>
|
||
{/* Tenants */}
|
||
<TableCell>
|
||
<Tooltip title={row.tenantadress}>
|
||
<Typography noWrap variant="subtitle1">
|
||
{row.tenantname}
|
||
</Typography>
|
||
<Typography noWrap variant="body2">
|
||
{row.tenantsuburb}
|
||
</Typography>
|
||
|
||
<Typography noWrap variant="body2">
|
||
{row.applocation}
|
||
</Typography>
|
||
</Tooltip>
|
||
</TableCell>
|
||
{/* order details */}
|
||
<TableCell align="left">
|
||
<Tooltip title="Location Name-Suburb" placement="top">
|
||
<Typography variant="subtitle1" noWrap>
|
||
{`${row.locationname}-(${row.locationsuburb})`}
|
||
</Typography>
|
||
</Tooltip>
|
||
|
||
<Stack display={'flex'} flexDirection={'row'} gap={3}>
|
||
<Stack>
|
||
<Tooltip title="Order Id" placement="top">
|
||
<Typography variant="body2" noWrap>
|
||
{row.orderid}
|
||
</Typography>
|
||
</Tooltip>
|
||
<Tooltip title="Ordered date" placement="top">
|
||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||
{dayjs(row.orderdate).utc().format('DD/MM/YYYY')}
|
||
</Typography>
|
||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||
{dayjs(row.orderdate).utc().format('hh:mm A')}
|
||
</Typography>
|
||
</Tooltip>
|
||
</Stack>
|
||
-
|
||
<Stack>
|
||
<Tooltip title="Delivery Id" placement="top">
|
||
<Typography variant="body2" noWrap>
|
||
{row.deliveryid}
|
||
</Typography>
|
||
</Tooltip>
|
||
<Tooltip title="Delivery date" placement="top">
|
||
<Typography noWrap sx={{ fontSize: '12px' }}>
|
||
{dayjs(row.deliverydate).utc().format('DD/MM/YYYY')}
|
||
</Typography>
|
||
|
||
<Typography noWrap sx={{ fontSize: '11px' }}>
|
||
{dayjs(row.deliverydate).utc().format('hh:mm A')}
|
||
</Typography>
|
||
</Tooltip>
|
||
</Stack>
|
||
</Stack>
|
||
</TableCell>
|
||
{/* pickup */}
|
||
<TableCell align="left">
|
||
<Stack direction={'row'} spacing={1}>
|
||
<Stack direction="column">
|
||
<Typography variant="subtitle1" sx={{ whiteSpace: 'nowrap' }}>
|
||
{row.pickupcustomer}
|
||
</Typography>
|
||
<Typography variant="body2">{row.pickupcontactno}</Typography>
|
||
<Tooltip title={row.Pickupaddress} sx={{ whiteSpace: 'nowrap' }}>
|
||
<Typography variant="body2">{row.pickuplocation || row.Pickupaddress.slice(0, 12) + '...'}</Typography>
|
||
</Tooltip>
|
||
</Stack>
|
||
</Stack>
|
||
</TableCell>
|
||
{/* drop */}
|
||
<TableCell align="left">
|
||
<Stack direction={'row'} spacing={1}>
|
||
<Stack direction="column">
|
||
<Typography variant="subtitle1" sx={{ whiteSpace: 'nowrap' }}>
|
||
{row.deliverycustomer}
|
||
</Typography>
|
||
<Typography variant="body2">{row.deliverycontactno}</Typography>
|
||
<Tooltip title={row.deliveryaddress}>
|
||
<Typography variant="body2" sx={{ whiteSpace: 'nowrap' }}>
|
||
{`${row.deliverylocation || row.deliveryaddress.slice(0, 12) + '...'}`}
|
||
</Typography>
|
||
</Tooltip>
|
||
</Stack>
|
||
</Stack>
|
||
</TableCell>
|
||
{/* rider */}
|
||
<TableCell>
|
||
<Typography variant="subtitle1" noWrap>
|
||
{row.ridername}
|
||
</Typography>
|
||
<Typography variant="subtitle2"> {`ID: ${row.userid}`}</Typography>
|
||
<Typography variant="subtitle2"> {row.ridercontact}</Typography>
|
||
</TableCell>
|
||
{/* Estimated Delivery Time */}
|
||
<TableCell align="left">
|
||
<Typography variant="subtitle1" noWrap>
|
||
{row.expecteddeliverytime ? dayjs(row.expecteddeliverytime).format('hh:mm A') : 'N/A'}
|
||
</Typography>
|
||
</TableCell>
|
||
{/* Transit Minutes */}
|
||
<TableCell align="left">
|
||
<Typography variant="subtitle1" noWrap>
|
||
{row.transitminutes || 0} min
|
||
</Typography>
|
||
</TableCell>
|
||
{/* kms */}
|
||
<TableCell>
|
||
<Stack direction={'column'} spacing={1}>
|
||
<Tooltip title=" KMS" placement="top">
|
||
<Chip label={row.kms || 0} size="small" variant="light" color="error" />
|
||
</Tooltip>
|
||
<Tooltip title="Actual KMS">
|
||
<Chip label={row.cumulativekms || 0} size="small" color="success" variant="light" />
|
||
</Tooltip>
|
||
</Stack>
|
||
</TableCell>
|
||
{/* amount */}
|
||
<TableCell align="left">
|
||
<Stack direction={'column'} spacing={1}>
|
||
<Tooltip title=" Delivery Charge" placement="top">
|
||
<Chip label={`₹ ${row.deliverycharges?.toFixed(2)}` || 0} size="small" variant="light" color="error" />
|
||
</Tooltip>
|
||
<Tooltip title="Delivery Amount">
|
||
<Chip label={`₹ ${row.deliveryamt?.toFixed(2)}` || 0} size="small" color="success" variant="light" />
|
||
</Tooltip>
|
||
</Stack>
|
||
</TableCell>
|
||
{/* notes */}
|
||
<TableCell>
|
||
<Typography>{row.notes}</Typography>
|
||
</TableCell>
|
||
{/* step */}
|
||
<TableCell align="center">
|
||
{row.step ? (
|
||
<Chip
|
||
label={row.step}
|
||
size="small"
|
||
color="primary"
|
||
variant="light"
|
||
sx={{ fontWeight: 700, minWidth: 28 }}
|
||
/>
|
||
) : (
|
||
<Typography variant="subtitle2" color="text.disabled">
|
||
—
|
||
</Typography>
|
||
)}
|
||
</TableCell>
|
||
{/* qty */}
|
||
<TableCell>
|
||
{row.Quantity ? (
|
||
<Typography variant="subtitle1" color={'error'} sx={{ whiteSpace: 'nowrap' }}>
|
||
{row.Quantity}
|
||
</Typography>
|
||
) : (
|
||
<Typography variant="subtitle1" sx={{ whiteSpace: 'nowrap' }}>
|
||
{' '}
|
||
{row.Quantity}
|
||
</Typography>
|
||
)}
|
||
</TableCell>
|
||
{/* {cash} */}
|
||
<TableCell>
|
||
{row.collectionamt ? (
|
||
<Typography variant="subtitle1" color={'error'} sx={{ whiteSpace: 'nowrap' }}>
|
||
{' '}
|
||
{`₹ ${row.collectionamt.toFixed(2)}`}{' '}
|
||
</Typography>
|
||
) : (
|
||
<Typography variant="subtitle1" sx={{ whiteSpace: 'nowrap' }}>
|
||
{' '}
|
||
{`₹ ${row.collectionamt.toFixed(2)}`}{' '}
|
||
</Typography>
|
||
)}
|
||
</TableCell>
|
||
{/* Action */}
|
||
{tabstatus !== 'Cancelled' && tabstatus !== 'Delivered' && (
|
||
<TableCell>
|
||
<Stack display={'flex'} flexDirection={'row'} justifyContent={'end'}>
|
||
{row.deliverytype == 'C' && (
|
||
<Tooltip title="Product details" placement="top">
|
||
<IconButton
|
||
color="primary"
|
||
onClick={() => {
|
||
if (productCollapse?.orderid === row.orderid) {
|
||
setProductCollapse(null); // Collapse if already open
|
||
setOrderHeaderId(null);
|
||
} else {
|
||
setProductCollapse(row); // Open if different
|
||
setOrderHeaderId(row.orderheaderid); // Open if different
|
||
}
|
||
}}
|
||
>
|
||
{productCollapse?.orderid === row.orderid ? <KeyboardArrowUpOutlined /> : <KeyboardArrowDownOutlined />}
|
||
</IconButton>
|
||
</Tooltip>
|
||
)}
|
||
|
||
<IconButton
|
||
color="primary"
|
||
size="small"
|
||
sx={{ bgcolor: 'primary.100' }}
|
||
onClick={(e) => handleMenuOpen(e, row)}
|
||
>
|
||
<EditOutlined />
|
||
</IconButton>
|
||
|
||
<Menu
|
||
anchorEl={menuAnchorEl}
|
||
open={menuOpen}
|
||
onClose={handleMenuClose}
|
||
anchorOrigin={{
|
||
vertical: 'bottom',
|
||
horizontal: 'right'
|
||
}}
|
||
transformOrigin={{
|
||
vertical: 'top',
|
||
horizontal: 'right'
|
||
}}
|
||
PaperProps={{
|
||
elevation: 4,
|
||
sx: {
|
||
minWidth: 200,
|
||
borderRadius: 1
|
||
}
|
||
}}
|
||
>
|
||
{selectedRow?.orderstatus !== 'delivered' && (
|
||
<MenuItem
|
||
onClick={() => {
|
||
notifyRiderMutation.mutate(selectedRow.userfcmtoken);
|
||
handleMenuClose();
|
||
}}
|
||
>
|
||
Notify Rider
|
||
</MenuItem>
|
||
)}
|
||
{['pending', 'accepted', 'arrived'].includes(selectedRow?.orderstatus) && (
|
||
<MenuItem
|
||
onClick={() => {
|
||
if (!appId) {
|
||
opentoast('Please select a location first!', 'warning');
|
||
locationRef.current?.focus();
|
||
return;
|
||
}
|
||
setChangeDialogOpen(true);
|
||
handleMenuClose();
|
||
}}
|
||
>
|
||
Change Rider
|
||
</MenuItem>
|
||
)}
|
||
{(roleid == 1 || roleid == 2) && (
|
||
<MenuItem
|
||
onClick={() => {
|
||
setKms(selectedRow.kms);
|
||
setCumulativeKms(selectedRow.cumulativekms);
|
||
setDeliverylat(selectedRow.droplat);
|
||
setDeliverylong(selectedRow.droplon);
|
||
setNotes(selectedRow.notes);
|
||
setDeliveryamount(selectedRow.deliveryamount);
|
||
setUpdateStatus(selectedRow.orderstatus || 'delivered');
|
||
setCurrentorder(selectedRow);
|
||
setDialogopen(true);
|
||
handleMenuClose();
|
||
}}
|
||
>
|
||
Update Delivery Status
|
||
</MenuItem>
|
||
)}
|
||
{selectedRow?.orderstatus !== 'cancelled' && selectedRow?.orderstatus !== 'delivered' && (
|
||
<MenuItem
|
||
sx={{ color: 'error.main' }}
|
||
onClick={() => {
|
||
setCancelDeliveryOpen(true);
|
||
handleMenuClose();
|
||
}}
|
||
>
|
||
Cancel Delivery
|
||
</MenuItem>
|
||
)}
|
||
</Menu>
|
||
</Stack>
|
||
</TableCell>
|
||
)}
|
||
</TableRow>
|
||
{productCollapse?.orderid === row?.orderid && (
|
||
<TableRow>
|
||
<TableCell
|
||
colSpan={11}
|
||
sx={{
|
||
p: 0,
|
||
border: 0,
|
||
'&:hover': {
|
||
backgroundColor: 'white' // or use 'inherit'
|
||
}
|
||
}}
|
||
>
|
||
<Collapse in={true} timeout="auto" unmountOnExit>
|
||
<Box
|
||
sx={{
|
||
width: '100%',
|
||
px: 2,
|
||
py: 2
|
||
}}
|
||
>
|
||
<Table size="small" sx={{ minWidth: 800 }}>
|
||
<TableHead>
|
||
<TableRow
|
||
sx={{
|
||
'&:hover': {
|
||
backgroundColor: 'transparent !important'
|
||
}
|
||
}}
|
||
>
|
||
<TableCell sx={{ fontWeight: 600 }}>S.No</TableCell>
|
||
<TableCell sx={{ fontWeight: 600 }}>Product Name</TableCell>
|
||
<TableCell sx={{ fontWeight: 600 }}>Description</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
||
Quantity
|
||
</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
||
Cost
|
||
</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
||
Price
|
||
</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
||
Tax
|
||
</TableCell>
|
||
<TableCell align="right" sx={{ fontWeight: 600 }}>
|
||
Amount
|
||
</TableCell>
|
||
</TableRow>
|
||
</TableHead>
|
||
|
||
<TableBody>
|
||
{orderdetails?.details?.map((product, index) => (
|
||
<TableRow
|
||
key={index}
|
||
sx={{
|
||
'&:hover': {
|
||
backgroundColor: 'transparent !important'
|
||
}
|
||
}}
|
||
>
|
||
<TableCell>{index + 1}</TableCell>
|
||
<TableCell>
|
||
<Stack direction="row" spacing={1} alignItems="center">
|
||
<img
|
||
src={product?.productimage || 'https://via.placeholder.com/40'} // fallback image
|
||
alt={product?.productname}
|
||
style={{
|
||
width: 40,
|
||
height: 40,
|
||
objectFit: 'cover',
|
||
borderRadius: 4
|
||
}}
|
||
/>
|
||
<Typography variant="body2" noWrap>
|
||
{product?.productname || 'Unnamed'}
|
||
</Typography>
|
||
</Stack>
|
||
</TableCell>
|
||
<TableCell>{product?.productdescription || '-'}</TableCell>
|
||
<TableCell align="right">{product?.orderqty || 0}</TableCell>
|
||
<TableCell align="right">₹ {product?.price || 0}</TableCell>
|
||
<TableCell align="right">₹ {(product?.productsumprice ?? 0).toFixed(2)}</TableCell>
|
||
<TableCell align="right">₹ {(product?.taxamount ?? 0).toFixed(2)}</TableCell>
|
||
<TableCell align="right">
|
||
₹ {(product?.productsumprice + product?.taxamount).toFixed(2) || 0}
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
<TableRow
|
||
sx={{
|
||
'&:hover': {
|
||
backgroundColor: 'transparent !important'
|
||
}
|
||
}}
|
||
>
|
||
<TableCell align="right" colSpan={7} sx={{}}>
|
||
Total Amount :
|
||
</TableCell>
|
||
<TableCell align="right" sx={{}}>
|
||
₹ {orderdetails?.pricedetails?.orderamount.toFixed(2)}
|
||
</TableCell>
|
||
</TableRow>
|
||
</TableBody>
|
||
</Table>
|
||
</Box>
|
||
</Collapse>
|
||
</TableCell>
|
||
</TableRow>
|
||
)}
|
||
</>
|
||
);
|
||
})}
|
||
{countSourceRows?.length != 0 && (
|
||
<TableRow>
|
||
<TableCell colSpan={15} rowSpan={3}>
|
||
<div ref={loadMoreRef} style={{ height: 40, textAlign: 'center' }}>
|
||
{countIsFetchingNext ? <LoaderWithImage /> : countHasNext ? <LoaderWithImage /> : 'No More Deliveries'}
|
||
</div>
|
||
</TableCell>
|
||
</TableRow>
|
||
)}
|
||
</TableBody>
|
||
</Table>
|
||
</TableContainer>
|
||
</MainCard>
|
||
|
||
{/* =============================== || cancel dialog || =============================== */}
|
||
<Dialog open={cancelDeliveryOpen} onClose={() => setCancelDeliveryOpen(false)} maxWidth="xs">
|
||
<DialogContent sx={{ mt: 2, my: 1 }}>
|
||
<Stack alignItems="center" spacing={3.5}>
|
||
<Avatar color="error" sx={{ width: 72, height: 72, fontSize: '1.75rem' }}>
|
||
<DeleteFilled />
|
||
</Avatar>
|
||
|
||
<Stack spacing={2}>
|
||
<Typography variant="h5" align="center">
|
||
Are you sure you want to cancel this delivery?
|
||
</Typography>
|
||
</Stack>
|
||
<TextField
|
||
fullWidth
|
||
label="Enter Feedback"
|
||
multiline
|
||
maxRows={4}
|
||
onChange={(e) => {
|
||
setCancelFeed(e.target.value);
|
||
}}
|
||
/>
|
||
<Stack direction="row" spacing={2} sx={{ width: 1 }}>
|
||
<Button
|
||
// disabled={cancelFeed == '' ? true : false}
|
||
fullWidth
|
||
color="error"
|
||
variant="contained"
|
||
onClick={() => {
|
||
{
|
||
cancelFeed == '' ? opentoast('Enter FeedBack', 'error') : cancelDelivery({ selectedRow, cancelFeed });
|
||
}
|
||
cancelridernotification();
|
||
}}
|
||
autoFocus
|
||
sx={{ border: '1px solid red' }}
|
||
>
|
||
Yes, Cancel
|
||
</Button>
|
||
<Button
|
||
fullWidth
|
||
onClick={() => {
|
||
setCancelDeliveryOpen(false);
|
||
}}
|
||
color="secondary"
|
||
variant="outlined"
|
||
>
|
||
No
|
||
</Button>
|
||
</Stack>
|
||
</Stack>
|
||
</DialogContent>
|
||
</Dialog>
|
||
{/* =============================== || change dialog || =============================== */}
|
||
<Dialog
|
||
open={changeDialogOpen}
|
||
onClose={() => setChangeDialogOpen(false)}
|
||
maxWidth="sm"
|
||
fullWidth
|
||
TransitionComponent={PopupTransition}
|
||
>
|
||
<DialogTitle sx={{ bgcolor: theme.palette.primary.main }}>
|
||
<Typography variant="h3" sx={{ color: 'white' }}>
|
||
Change Rider
|
||
</Typography>
|
||
</DialogTitle>
|
||
<DialogContent dividers>
|
||
<Grid container spacing={2}>
|
||
<Grid item xs={12} sx={{ m: 2 }}>
|
||
<Autocomplete
|
||
fullWidth
|
||
options={ridersList}
|
||
// getOptionLabel={(option) => `${option.firstname} ${option.lastname} | ${option.contactno}`}
|
||
renderInput={(params) => <TextField {...params} label="Choose Rider" />}
|
||
onChange={(e, value) => {
|
||
setSelectedRider(value);
|
||
logger.debug('Rider selected in dropdown:', value ? `${value.firstname} ${value.lastname}` : 'None');
|
||
}}
|
||
/>
|
||
</Grid>
|
||
</Grid>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Stack direction={'row'} spacing={3}>
|
||
<Button
|
||
variant="outlined"
|
||
color="secondary"
|
||
onClick={() => {
|
||
setChangeDialogOpen(false);
|
||
}}
|
||
>
|
||
Cancel
|
||
</Button>
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
onClick={() => {
|
||
setLoading1(true);
|
||
logger.info(`Initiating rider assignment change for order ID ${selectedRow?.orderid} to rider: ${selectedRider?.firstname} ${selectedRider?.lastname}`);
|
||
changeRiderMutation.mutate({ selectedRider, selectedRow });
|
||
}}
|
||
>
|
||
Change Rider
|
||
</Button>
|
||
</Stack>
|
||
</DialogActions>
|
||
</Dialog>
|
||
|
||
{/* =============================== || Date filter Dialog || =============================== */}
|
||
<Dialog open={open}>
|
||
<DialogTitle align="left">
|
||
<Typography variant="h4">Select Filter Options</Typography>
|
||
</DialogTitle>
|
||
<DialogContent sx={{ width: '100%' }} className="datedialog">
|
||
<DateRangePicker
|
||
open={open}
|
||
toggle={() => 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'));
|
||
if (range.label) {
|
||
setDatestatus(range.label);
|
||
} else {
|
||
setDatestatus('');
|
||
}
|
||
}
|
||
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))
|
||
}
|
||
// {
|
||
// label: 'All',
|
||
// startDate: new Date(),
|
||
// endDate: addDays(new Date(), -1),
|
||
// },
|
||
]}
|
||
/>
|
||
</DialogContent>
|
||
<Stack direction="row" justifyContent="flex-end" sx={{ width: '100%', p: 2 }}>
|
||
<Button variant="contained" size="small" onClick={okclicked}>
|
||
ok
|
||
</Button>
|
||
</Stack>
|
||
</Dialog>
|
||
|
||
{/* =============================== || Update Delivery Dialog || =============================== */}
|
||
<Dialog fullWidth={true} open={dialogopen} onClose={dialogclose} scroll={'paper'} maxWidth="sm" TransitionComponent={PopupTransition}>
|
||
<DialogTitle
|
||
sx={{
|
||
bgcolor: '#662582',
|
||
color: '#fff'
|
||
}}
|
||
>
|
||
Update Delivery Status
|
||
</DialogTitle>
|
||
|
||
<DialogContent dividers={true}>
|
||
<MainCard sx={{ height: '100%' }}>
|
||
<Grid container spacing={2.5}>
|
||
<Grid item xs={12} sm={6}>
|
||
<FormLabel>Kms</FormLabel>
|
||
<TextField
|
||
fullWidth
|
||
value={kms}
|
||
type="number"
|
||
onChange={(e) => {
|
||
setKms(e.target.value);
|
||
console.log(e);
|
||
}}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} sm={6}>
|
||
<FormLabel>Actual Kms</FormLabel>
|
||
<TextField value={cumulativekms} type="number" onChange={(e) => setCumulativeKms(+e.target.value)} fullWidth />
|
||
</Grid>
|
||
<Grid item xs={12} sm={6}>
|
||
<FormLabel>Delivery lat</FormLabel>
|
||
<TextField
|
||
fullWidth
|
||
type="number"
|
||
value={deliverylat}
|
||
onChange={(e) => {
|
||
setDeliverylat(e.target.value);
|
||
}}
|
||
/>
|
||
</Grid>
|
||
<Grid item xs={12} sm={6}>
|
||
<FormLabel>Delivery Long</FormLabel>
|
||
<TextField
|
||
value={deliverylong}
|
||
type="number"
|
||
onChange={(e) => {
|
||
setDeliverylong(e.target.value);
|
||
}}
|
||
fullWidth
|
||
/>
|
||
</Grid>
|
||
|
||
<Grid item xs={12}>
|
||
<FormLabel>Amount</FormLabel>
|
||
|
||
<TextField fullWidth type="number" value={deliveryamount} onChange={(e) => setDeliveryamount(+e.target.value)} />
|
||
</Grid>
|
||
<Grid item xs={12}>
|
||
<FormLabel>Status</FormLabel>
|
||
<TextField
|
||
select
|
||
fullWidth
|
||
value={updateStatus}
|
||
onChange={(e) => {
|
||
setUpdateStatus(e.target.value);
|
||
}}
|
||
>
|
||
<MenuItem value="pending">Pending</MenuItem>
|
||
<MenuItem value="accepted">Accepted</MenuItem>
|
||
<MenuItem value="started">Started</MenuItem>
|
||
<MenuItem value="arrived">Arrived</MenuItem>
|
||
<MenuItem value="delivered">Delivered</MenuItem>
|
||
<MenuItem value="cancelled">Cancelled</MenuItem>
|
||
</TextField>
|
||
</Grid>
|
||
<Grid item xs={12}>
|
||
<FormLabel>Notes</FormLabel>
|
||
<TextField value={notes} fullWidth onChange={(e) => setNotes(e.target.value)} />
|
||
</Grid>
|
||
</Grid>
|
||
</MainCard>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Grid container>
|
||
<Grid item xs={12}>
|
||
<Stack direction={'row'} justifyContent={'flex-end'} spacing={2} sx={{ p: 2 }}>
|
||
<Button
|
||
variant="contained"
|
||
onClick={() => {
|
||
dialogclose();
|
||
}}
|
||
color="error"
|
||
>
|
||
Close
|
||
</Button>{' '}
|
||
<Button
|
||
variant="contained"
|
||
onClick={() => {
|
||
if (!kms) {
|
||
opentoast('Fill Kms', 'warning');
|
||
} else if (!cumulativekms) {
|
||
opentoast('Fill Actual Kms', 'warning');
|
||
} else if (!deliverylat) {
|
||
opentoast('Fill Delivery Latitude', 'warning');
|
||
} else if (!deliverylong) {
|
||
opentoast('Fill Delivery Longitude', 'warning');
|
||
} else if (!deliveryamount) {
|
||
opentoast('Fill Delivery Amount', 'warning');
|
||
} else {
|
||
updateDeliveryMutation.mutate({
|
||
deliveryid: currentorder.deliveryid,
|
||
orderheaderid: currentorder.orderheaderid,
|
||
orderstatus: updateStatus,
|
||
deliverytime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||
deliverylat,
|
||
deliverylong,
|
||
kms,
|
||
cumulativekms,
|
||
deliveryamt: parseFloat(deliveryamount),
|
||
notes,
|
||
userid: +selectedRow.userid
|
||
});
|
||
}
|
||
}}
|
||
>
|
||
Update
|
||
</Button>
|
||
</Stack>
|
||
</Grid>
|
||
</Grid>
|
||
</DialogActions>
|
||
</Dialog>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default Deliveries;
|