updates on the create order page ui changes
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import logger from './utils/logger';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
@@ -38,6 +39,8 @@ if (process.env.NODE_ENV !== 'development') {
|
||||
console.warn = () => {}; // Optionally disable console.warn
|
||||
}
|
||||
|
||||
logger.info('NearlExpress console application starting...');
|
||||
|
||||
// const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
|
||||
// ==============================|| MAIN - REACT DOM RENDER ||============================== //
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
@@ -22,6 +23,7 @@ const MainLayout = () => {
|
||||
const matchDownXL = useMediaQuery(theme.breakpoints.down('xl'));
|
||||
const downLG = useMediaQuery(theme.breakpoints.down('lg'));
|
||||
|
||||
const { drawerOpen } = useSelector((state) => state.menu);
|
||||
const { container, miniDrawer, menuOrientation } = useConfig();
|
||||
|
||||
const isHorizontal = menuOrientation === MenuOrientation.HORIZONTAL && !downLG;
|
||||
@@ -39,7 +41,14 @@ const MainLayout = () => {
|
||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
||||
<Header />
|
||||
{!isHorizontal ? <Drawer /> : <HorizontalBar />}
|
||||
<Box component="main" sx={{ width: 'calc(100% - 260px)', flexGrow: 1, p: { xs: 2, sm: 3 } }}>
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
width: isHorizontal ? '100%' : drawerOpen ? 'calc(100% - 260px)' : { xs: '100%', lg: 'calc(100% - 60px)' },
|
||||
flexGrow: 1,
|
||||
p: { xs: 2, sm: 3 }
|
||||
}}
|
||||
>
|
||||
<Toolbar sx={{ mt: isHorizontal ? 8 : 'inherit' }} />
|
||||
<Container
|
||||
// maxWidth={container ? 'xl' : false}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logger from '../../../utils/logger';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { DeleteFilled, EditOutlined } from '@ant-design/icons';
|
||||
import { useState, useEffect, Fragment, useRef } from 'react';
|
||||
@@ -258,18 +259,19 @@ const Deliveries = () => {
|
||||
// =========================================== || changerider || ===========================================
|
||||
const changeRiderMutation = useMutation({
|
||||
mutationFn: ({ selectedRider, selectedRow }) => changeRiderAPI(selectedRider, selectedRow),
|
||||
onSuccess: (res) => {
|
||||
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
|
||||
notifyRiderMutation.mutate(selectedRider.userfcmtoken);
|
||||
},
|
||||
onError: (err) => {
|
||||
console.log(err);
|
||||
onError: (err, { selectedRider, selectedRow }) => {
|
||||
logger.error(`Failed to change rider for order ID ${selectedRow?.orderid}:`, err);
|
||||
opentoast(err.message, 'error');
|
||||
setLoading1(false);
|
||||
}
|
||||
@@ -1236,7 +1238,7 @@ const Deliveries = () => {
|
||||
Notify Rider
|
||||
</MenuItem>
|
||||
)}
|
||||
{['pending', 'accepted', 'arrived'].includes(selectedRow?.orderstatus) && (
|
||||
{/* {['pending', 'accepted', 'arrived'].includes(selectedRow?.orderstatus) && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
if (!appId) {
|
||||
@@ -1250,7 +1252,7 @@ const Deliveries = () => {
|
||||
>
|
||||
Change Rider
|
||||
</MenuItem>
|
||||
)}
|
||||
)} */}
|
||||
{(roleid == 1 || roleid == 2) && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
@@ -1487,7 +1489,7 @@ const Deliveries = () => {
|
||||
renderInput={(params) => <TextField {...params} label="Choose Rider" />}
|
||||
onChange={(e, value) => {
|
||||
setSelectedRider(value);
|
||||
console.log('selected rider', value);
|
||||
logger.debug('Rider selected in dropdown:', value ? `${value.firstname} ${value.lastname}` : 'None');
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -1509,6 +1511,7 @@ const Deliveries = () => {
|
||||
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 });
|
||||
}}
|
||||
>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,7 @@ import {
|
||||
} from './dispatchShared';
|
||||
import CompareDataPanel from './CompareDataPanel';
|
||||
import './Dispatch.css';
|
||||
import logger from '../../../utils/logger';
|
||||
|
||||
// Combined-mode rail colors. The per-step palette (STEP_PALETTE) is great for
|
||||
// "which step is this" but useless for "is this line planned or actual" when
|
||||
@@ -709,11 +710,11 @@ const Dispatch = ({
|
||||
// marker; clicking the marker again unpins it. Stored in a ref because this
|
||||
// only drives imperative leaflet calls — no re-render needed.
|
||||
const pinnedPopupsRef = useRef(new Set());
|
||||
// Short-lived close timer for the Compare drop-pin popup. Gives the
|
||||
// cursor a ~120ms window to travel from the marker onto the popup
|
||||
// without firing mouseout → closePopup mid-transit. Any new mouseover
|
||||
// cancels a pending close, so the popup glides instead of flickering.
|
||||
const compareHoverTimerRef = useRef(null);
|
||||
// Short-lived close timer for the general map order/marker popups.
|
||||
// Gives the cursor a ~200ms window to travel from the marker onto the popup
|
||||
// or vice versa without immediately triggering a close.
|
||||
const activePopupMarkerRef = useRef(null);
|
||||
const popupHoverTimerRef = useRef(null);
|
||||
const isControlled = selectedRiderId !== undefined;
|
||||
const [clock, setClock] = useState('');
|
||||
|
||||
@@ -898,6 +899,7 @@ const Dispatch = ({
|
||||
}, [appLocations, selectedAppLocationId]);
|
||||
|
||||
const handleLocationPick = (id) => {
|
||||
logger.info('Switching hub/location ID:', id);
|
||||
setSelectedAppLocationId(Number(id));
|
||||
setLocationMenuOpen(false);
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -1383,6 +1385,53 @@ const Dispatch = ({
|
||||
[isControlled, onRiderSelect]
|
||||
);
|
||||
|
||||
// ─── Logger Effects for Rider & Order Updates ───────────────────────
|
||||
|
||||
// Log when the active focused rider changes (uncontrolled click or controlled prop change)
|
||||
const prevFocusedRiderIdRef = useRef(null);
|
||||
useEffect(() => {
|
||||
const activeId = focusedRider ? focusedRider.id : null;
|
||||
if (activeId !== prevFocusedRiderIdRef.current) {
|
||||
if (focusedRider) {
|
||||
logger.info(`Focused rider changed to: ${focusedRider.riderName} (${focusedRider.orders.length} orders)`);
|
||||
} else {
|
||||
logger.info('Focused rider reset to: None');
|
||||
}
|
||||
prevFocusedRiderIdRef.current = activeId;
|
||||
}
|
||||
}, [focusedRider]);
|
||||
|
||||
// Log when selected focused stop (individual order) changes
|
||||
useEffect(() => {
|
||||
if (focusedStop) {
|
||||
logger.info(`Focused order updated: ID ${focusedStop.orderid}`);
|
||||
} else {
|
||||
logger.debug('Focused order selection cleared');
|
||||
}
|
||||
}, [focusedStop]);
|
||||
|
||||
// Log when periodic query refetch completes and updates the overall orders list
|
||||
const prevOrdersCountRef = useRef(0);
|
||||
useEffect(() => {
|
||||
if (allOrders) {
|
||||
if (allOrders.length !== prevOrdersCountRef.current) {
|
||||
logger.info(`Orders database updated: ${allOrders.length} orders actively tracked`);
|
||||
prevOrdersCountRef.current = allOrders.length;
|
||||
}
|
||||
}
|
||||
}, [allOrders]);
|
||||
|
||||
// Log when periodic query refetch completes and updates the live rider locations
|
||||
const prevRidersCountRef = useRef(0);
|
||||
useEffect(() => {
|
||||
if (liveRiderLocations) {
|
||||
if (liveRiderLocations.length !== prevRidersCountRef.current) {
|
||||
logger.info(`Live riders list updated: ${liveRiderLocations.length} active riders mapped`);
|
||||
prevRidersCountRef.current = liveRiderLocations.length;
|
||||
}
|
||||
}
|
||||
}, [liveRiderLocations]);
|
||||
|
||||
const activeStats = useMemo(() => {
|
||||
if (focusedRider) {
|
||||
return {
|
||||
@@ -2127,8 +2176,44 @@ const Dispatch = ({
|
||||
// chips — operators learn the layout once and trust it everywhere.
|
||||
const renderOrderPopupContent = (o) => {
|
||||
const statusStyle = getStatusStyle(o.orderstatus);
|
||||
|
||||
const isPinned = () => {
|
||||
if (pinnedPopupsRef.current.has(String(o.orderid))) return true;
|
||||
if (compareOpen && focusedRider && o.deliveryid != null) {
|
||||
const track = riderActualTracks.find((t) => String(t.deliveryid) === String(o.deliveryid));
|
||||
if (track && focusedCompareStep === track.sequenceStep) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (popupHoverTimerRef.current) {
|
||||
clearTimeout(popupHoverTimerRef.current);
|
||||
popupHoverTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (isPinned()) return;
|
||||
|
||||
if (popupHoverTimerRef.current) {
|
||||
clearTimeout(popupHoverTimerRef.current);
|
||||
}
|
||||
popupHoverTimerRef.current = setTimeout(() => {
|
||||
if (activePopupMarkerRef.current) {
|
||||
activePopupMarkerRef.current.closePopup();
|
||||
activePopupMarkerRef.current = null;
|
||||
}
|
||||
popupHoverTimerRef.current = null;
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
>
|
||||
<div className="pu-header">
|
||||
<div className="pu-header-top">
|
||||
<div className="pu-id">ORDER #{o.orderid}</div>
|
||||
@@ -2234,7 +2319,7 @@ const Dispatch = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2318,11 +2403,29 @@ const Dispatch = ({
|
||||
else delete orderMarkerRefs.current[String(o.orderid)];
|
||||
}}
|
||||
eventHandlers={{
|
||||
mouseover: (e) => e.target.openPopup(),
|
||||
mouseout: (e) => {
|
||||
if (!pinnedPopupsRef.current.has(String(o.orderid))) {
|
||||
e.target.closePopup();
|
||||
mouseover: (e) => {
|
||||
const marker = e.target;
|
||||
if (popupHoverTimerRef.current) {
|
||||
clearTimeout(popupHoverTimerRef.current);
|
||||
popupHoverTimerRef.current = null;
|
||||
}
|
||||
activePopupMarkerRef.current = marker;
|
||||
marker.openPopup();
|
||||
},
|
||||
mouseout: (e) => {
|
||||
const marker = e.target;
|
||||
if (pinnedPopupsRef.current.has(String(o.orderid))) return;
|
||||
|
||||
if (popupHoverTimerRef.current) {
|
||||
clearTimeout(popupHoverTimerRef.current);
|
||||
}
|
||||
popupHoverTimerRef.current = setTimeout(() => {
|
||||
marker.closePopup();
|
||||
if (activePopupMarkerRef.current === marker) {
|
||||
activePopupMarkerRef.current = null;
|
||||
}
|
||||
popupHoverTimerRef.current = null;
|
||||
}, 200);
|
||||
},
|
||||
click: (e) => {
|
||||
const id = String(o.orderid);
|
||||
@@ -2336,7 +2439,7 @@ const Dispatch = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Popup maxWidth={520} minWidth={460} className="dispatch-popup" autoPan={false}>
|
||||
<Popup maxWidth={520} minWidth={460} className="dispatch-popup" autoPan={true} autoPanPadding={[40, 40]}>
|
||||
{renderOrderPopupContent(o)}
|
||||
</Popup>
|
||||
</Marker>
|
||||
@@ -2836,13 +2939,13 @@ const Dispatch = ({
|
||||
)}
|
||||
|
||||
<div id="strat-row">
|
||||
<button className={`sbt ${viewMode === 'kitchens' ? 'active' : ''}`} onClick={() => { setViewMode('kitchens'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdPlace /></span> By Location</button>
|
||||
<button className={`sbt ${viewMode === 'kitchens' ? 'active' : ''}`} onClick={() => { logger.info('View mode changed: By Location'); setViewMode('kitchens'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdPlace /></span> By Location</button>
|
||||
<button
|
||||
className={`sbt ${viewMode === 'zones' ? 'active' : ''}`}
|
||||
onClick={() => { setViewMode('zones'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}
|
||||
onClick={() => { logger.info('View mode changed: By Zone'); setViewMode('zones'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}
|
||||
><span className="sbt-icon"><MdMap /></span> By Zone</button>
|
||||
<button className={`sbt ${viewMode === 'riders' ? 'active' : ''}`} onClick={() => { setViewMode('riders'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdDirectionsBike /></span> By Rider</button>
|
||||
<button className={`sbt ${viewMode === 'all' ? 'active' : ''}`} onClick={() => { setViewMode('all'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdPublic /></span> All Routes</button>
|
||||
<button className={`sbt ${viewMode === 'riders' ? 'active' : ''}`} onClick={() => { logger.info('View mode changed: By Rider'); setViewMode('riders'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdDirectionsBike /></span> By Rider</button>
|
||||
<button className={`sbt ${viewMode === 'all' ? 'active' : ''}`} onClick={() => { logger.info('View mode changed: All Routes'); setViewMode('all'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdPublic /></span> All Routes</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`sbt sbt-rider-info ${viewMode === 'rider-info' ? 'active' : ''}`}
|
||||
@@ -3886,7 +3989,7 @@ const Dispatch = ({
|
||||
mouseout: (e) => e.target.closePopup()
|
||||
}}
|
||||
>
|
||||
<Popup className="kitchen-popup" maxWidth={220} minWidth={200} autoPan={false}>
|
||||
<Popup className="kitchen-popup" maxWidth={220} minWidth={200} autoPan={true} autoPanPadding={[20, 20]}>
|
||||
<div className="kp-header">KITCHEN</div>
|
||||
<div className="kp-name">{k.kitchenName}</div>
|
||||
<div className="kp-stat">
|
||||
@@ -3931,7 +4034,7 @@ const Dispatch = ({
|
||||
mouseout: (e) => e.target.closePopup()
|
||||
}}
|
||||
>
|
||||
<Popup maxWidth={240} autoPan={false} className="dispatch-popup route-rider-popup">
|
||||
<Popup maxWidth={240} autoPan={true} autoPanPadding={[20, 20]} className="dispatch-popup route-rider-popup">
|
||||
<div className="pu-hdr-live">
|
||||
<div className="pu-hdr-left">
|
||||
<span className="pu-hdr-title">RIDER ROUTE</span>
|
||||
@@ -4011,7 +4114,7 @@ const Dispatch = ({
|
||||
mouseout: (e) => e.target.closePopup()
|
||||
}}
|
||||
>
|
||||
<Popup maxWidth={260} autoPan={false} className="dispatch-popup live-rider-popup">
|
||||
<Popup maxWidth={260} autoPan={true} autoPanPadding={[20, 20]} className="dispatch-popup live-rider-popup">
|
||||
<div className="pu-hdr-live">
|
||||
<div className="pu-hdr-left">
|
||||
<span className="pu-live-indicator" style={{ '--pulse-color': pinColor }}>
|
||||
@@ -4272,25 +4375,30 @@ const Dispatch = ({
|
||||
// the rich order popup, leaving it pinned while
|
||||
// a step is focused (so click-to-focus keeps the
|
||||
// modal visible after the cursor moves away). The
|
||||
// ~120ms grace timer on mouseout lets the cursor
|
||||
// ~200ms grace timer on mouseout lets the cursor
|
||||
// travel onto the popup itself without flicker.
|
||||
mouseover: (e) => {
|
||||
if (compareHoverTimerRef.current) {
|
||||
clearTimeout(compareHoverTimerRef.current);
|
||||
compareHoverTimerRef.current = null;
|
||||
const marker = e.target;
|
||||
if (popupHoverTimerRef.current) {
|
||||
clearTimeout(popupHoverTimerRef.current);
|
||||
popupHoverTimerRef.current = null;
|
||||
}
|
||||
e.target.openPopup();
|
||||
activePopupMarkerRef.current = marker;
|
||||
marker.openPopup();
|
||||
},
|
||||
mouseout: (e) => {
|
||||
if (focusedCompareStep === t.sequenceStep) return;
|
||||
const marker = e.target;
|
||||
if (compareHoverTimerRef.current) {
|
||||
clearTimeout(compareHoverTimerRef.current);
|
||||
if (popupHoverTimerRef.current) {
|
||||
clearTimeout(popupHoverTimerRef.current);
|
||||
}
|
||||
compareHoverTimerRef.current = setTimeout(() => {
|
||||
popupHoverTimerRef.current = setTimeout(() => {
|
||||
marker.closePopup();
|
||||
compareHoverTimerRef.current = null;
|
||||
}, 120);
|
||||
if (activePopupMarkerRef.current === marker) {
|
||||
activePopupMarkerRef.current = null;
|
||||
}
|
||||
popupHoverTimerRef.current = null;
|
||||
}, 200);
|
||||
},
|
||||
click: handleEndMarkerClick
|
||||
}
|
||||
@@ -4349,7 +4457,8 @@ const Dispatch = ({
|
||||
maxWidth={520}
|
||||
minWidth={460}
|
||||
className="dispatch-popup"
|
||||
autoPan={false}
|
||||
autoPan={true}
|
||||
autoPanPadding={[40, 40]}
|
||||
>
|
||||
{renderOrderPopupContent(orderForTrack)}
|
||||
</Popup>
|
||||
|
||||
1302
src/pages/nearle/orders/OrdersRedesign.css
Normal file
1302
src/pages/nearle/orders/OrdersRedesign.css
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -58,6 +58,226 @@ import { TimePicker } from '@mui/x-date-pickers';
|
||||
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||
import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
||||
import { MapContainer, TileLayer, Marker, Polyline, useMap } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
const pickupIcon = typeof window !== 'undefined' ? new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41]
|
||||
}) : null;
|
||||
|
||||
const dropoffIcon = typeof window !== 'undefined' ? new L.Icon({
|
||||
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
||||
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41]
|
||||
}) : null;
|
||||
|
||||
const MapBoundsController = ({ startPoint, endPoint }) => {
|
||||
const map = useMap();
|
||||
useEffect(() => {
|
||||
const points = [];
|
||||
const pLat = parseFloat(startPoint?.latitude);
|
||||
const pLng = parseFloat(startPoint?.longitude);
|
||||
const dLat = parseFloat(endPoint?.latitude);
|
||||
const dLng = parseFloat(endPoint?.longitude);
|
||||
|
||||
if (!isNaN(pLat) && !isNaN(pLng) && pLat !== 0 && pLng !== 0) {
|
||||
points.push([pLat, pLng]);
|
||||
}
|
||||
if (!isNaN(dLat) && !isNaN(dLng) && dLat !== 0 && dLng !== 0) {
|
||||
points.push([dLat, dLng]);
|
||||
}
|
||||
|
||||
if (points.length === 1) {
|
||||
map.setView(points[0], 14);
|
||||
} else if (points.length === 2) {
|
||||
map.fitBounds(points, { padding: [50, 50] });
|
||||
}
|
||||
}, [startPoint?.latitude, startPoint?.longitude, endPoint?.latitude, endPoint?.longitude, map]);
|
||||
return null;
|
||||
};
|
||||
|
||||
const OrderMap = ({ startPoint, endPoint, appLocaLat, appLocaLng }) => {
|
||||
const defaultCenter = [
|
||||
parseFloat(appLocaLat) || 11.0168,
|
||||
parseFloat(appLocaLng) || 76.9558
|
||||
];
|
||||
|
||||
const hasPick = startPoint?.latitude && startPoint?.longitude && parseFloat(startPoint.latitude) !== 0;
|
||||
const hasDrop = endPoint?.latitude && endPoint?.longitude && parseFloat(endPoint.latitude) !== 0;
|
||||
|
||||
const pickCoords = hasPick ? [parseFloat(startPoint.latitude), parseFloat(startPoint.longitude)] : null;
|
||||
const dropCoords = hasDrop ? [parseFloat(endPoint.latitude), parseFloat(endPoint.longitude)] : null;
|
||||
|
||||
// ---- Animation state (mirrors Dispatch.js pattern) ----
|
||||
const [osrmRoute, setOsrmRoute] = useState(null); // full road path [[lat,lng],...]
|
||||
const [animatedSegments, setAnimatedSegments] = useState([]); // drawn pair-by-pair
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const isAnimatingRef = useRef(false);
|
||||
const timeoutsRef = useRef([]);
|
||||
|
||||
// Fetch OSRM route whenever both points are available
|
||||
useEffect(() => {
|
||||
if (!hasPick || !hasDrop) {
|
||||
setOsrmRoute(null);
|
||||
setAnimatedSegments([]);
|
||||
setIsAnimating(false);
|
||||
return;
|
||||
}
|
||||
const url = `https://router.project-osrm.org/route/v1/driving/${pickCoords[1]},${pickCoords[0]};${dropCoords[1]},${dropCoords[0]}?overview=full&geometries=geojson`;
|
||||
fetch(url)
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
const coords = data?.routes?.[0]?.geometry?.coordinates;
|
||||
if (coords && coords.length >= 2) {
|
||||
// OSRM returns [lng, lat], Leaflet needs [lat, lng]
|
||||
setOsrmRoute(coords.map(([lng, lat]) => [lat, lng]));
|
||||
} else {
|
||||
// Fallback to straight line
|
||||
setOsrmRoute([pickCoords, dropCoords]);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setOsrmRoute([pickCoords, dropCoords]);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [startPoint?.latitude, startPoint?.longitude, endPoint?.latitude, endPoint?.longitude]);
|
||||
|
||||
// Reset animation when route changes
|
||||
useEffect(() => {
|
||||
setAnimatedSegments([]);
|
||||
setIsAnimating(false);
|
||||
isAnimatingRef.current = false;
|
||||
timeoutsRef.current.forEach(clearTimeout);
|
||||
timeoutsRef.current = [];
|
||||
}, [osrmRoute]);
|
||||
|
||||
const startAnimation = () => {
|
||||
// Stop if already animating
|
||||
if (isAnimating) {
|
||||
setIsAnimating(false);
|
||||
isAnimatingRef.current = false;
|
||||
setAnimatedSegments([]);
|
||||
timeoutsRef.current.forEach(clearTimeout);
|
||||
timeoutsRef.current = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const path = osrmRoute || (hasPick && hasDrop ? [pickCoords, dropCoords] : null);
|
||||
if (!path || path.length < 2) return;
|
||||
|
||||
setIsAnimating(true);
|
||||
isAnimatingRef.current = true;
|
||||
setAnimatedSegments([]);
|
||||
timeoutsRef.current.forEach(clearTimeout);
|
||||
timeoutsRef.current = [];
|
||||
|
||||
// Build segments same way as Dispatch.js: path[i] → path[i+1], each with a delay
|
||||
const allSegs = [];
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
allSegs.push({ from: path[i], to: path[i + 1], delay: i * 18 });
|
||||
}
|
||||
|
||||
allSegs.forEach((s, idx) => {
|
||||
const t = setTimeout(() => {
|
||||
if (!isAnimatingRef.current) return;
|
||||
setAnimatedSegments((prev) => [...prev, s]);
|
||||
if (idx === allSegs.length - 1) {
|
||||
const finalT = setTimeout(() => {
|
||||
setIsAnimating(false);
|
||||
isAnimatingRef.current = false;
|
||||
}, 800);
|
||||
timeoutsRef.current.push(finalT);
|
||||
}
|
||||
}, s.delay);
|
||||
timeoutsRef.current.push(t);
|
||||
});
|
||||
};
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => () => {
|
||||
timeoutsRef.current.forEach(clearTimeout);
|
||||
isAnimatingRef.current = false;
|
||||
}, []);
|
||||
|
||||
const staticRoute = osrmRoute || (hasPick && hasDrop ? [pickCoords, dropCoords] : null);
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%', minHeight: '350px' }}>
|
||||
<MapContainer
|
||||
center={defaultCenter}
|
||||
zoom={12}
|
||||
style={{ width: '100%', height: '100%', minHeight: '350px' }}
|
||||
zoomControl={true}
|
||||
>
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution='© OpenStreetMap contributors'
|
||||
/>
|
||||
{hasPick && <Marker position={pickCoords} icon={pickupIcon} />}
|
||||
{hasDrop && <Marker position={dropCoords} icon={dropoffIcon} />}
|
||||
|
||||
{/* Static solid polyline (shown when not animating) */}
|
||||
{!isAnimating && staticRoute && staticRoute.length >= 2 && (
|
||||
<Polyline
|
||||
positions={staticRoute}
|
||||
pathOptions={{ color: '#1890ff', weight: 4, opacity: 0.85, lineJoin: 'round', lineCap: 'round' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Animated segments — drawn pair-by-pair, matching Dispatch.js pattern */}
|
||||
{isAnimating && animatedSegments.map((s, i) => (
|
||||
<Polyline
|
||||
key={i}
|
||||
positions={[s.from, s.to]}
|
||||
pathOptions={{ color: '#1890ff', weight: 5, opacity: 0.95, lineJoin: 'round', lineCap: 'round' }}
|
||||
/>
|
||||
))}
|
||||
|
||||
<MapBoundsController startPoint={startPoint} endPoint={endPoint} />
|
||||
</MapContainer>
|
||||
|
||||
{/* Floating Animate Routes button — same style as Dispatch's sbt button */}
|
||||
{hasPick && hasDrop && (
|
||||
<button
|
||||
onClick={startAnimation}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '16px',
|
||||
right: '16px',
|
||||
zIndex: 1000,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
padding: '8px 14px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
fontWeight: 600,
|
||||
fontSize: '13px',
|
||||
boxShadow: '0 4px 14px rgba(0,0,0,0.18)',
|
||||
background: isAnimating ? '#1890ff' : '#ffffff',
|
||||
color: isAnimating ? '#ffffff' : '#1a202c',
|
||||
transition: 'all 0.25s ease',
|
||||
letterSpacing: '0.01em'
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '14px' }}>{isAnimating ? '⏹' : '▶'}</span>
|
||||
{isAnimating ? 'Stop' : 'Animate Route'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
function loadScript(src, position, id) {
|
||||
if (!position) {
|
||||
@@ -145,6 +365,8 @@ const Createorder1 = () => {
|
||||
const [pickDialog, setPickDialog] = useState(false);
|
||||
const [deliverydate, setDeliverydate] = useState(dayjs().add(30, 'minute'));
|
||||
const [deliveryDialog, setDeliveryDialog] = useState(false);
|
||||
const [pickupSearchOpen, setPickupSearchOpen] = useState(false);
|
||||
const [dropSearchOpen, setDropSearchOpen] = useState(false);
|
||||
|
||||
const pickerRef = useRef(null);
|
||||
const deliveryRef = useRef(null);
|
||||
@@ -319,7 +541,7 @@ const Createorder1 = () => {
|
||||
if (tenantid) {
|
||||
clientdetails();
|
||||
}
|
||||
}, [searchCustList?.length > 3, searchCustList == '', tenantid]);
|
||||
}, [searchCustList?.length > 3, searchCustList == '', tenantid, pickupSearchOpen, dropSearchOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (timeslotarr[0]) {
|
||||
@@ -1037,6 +1259,10 @@ const Createorder1 = () => {
|
||||
</Stack>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2} sx={{ height: '100%' }}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={8}>
|
||||
<Grid container spacing={2} sx={{ height: '100%' }}>
|
||||
{/* ================================================= || Pickup || ================================================= */}
|
||||
|
||||
@@ -1044,13 +1270,18 @@ const Createorder1 = () => {
|
||||
<MainCard sx={{ height: '100%' }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction={'row'} justifyContent={'space-between'}>
|
||||
<Typography variant="h5" sx={{ mb: 2 }}>
|
||||
<Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'}>
|
||||
<Typography variant="h5" sx={{ mb: 0 }}>
|
||||
Pickup Details
|
||||
</Typography>
|
||||
<Stack display={'flex'} direction={'row'}>
|
||||
<Stack display={'flex'} direction={'row'} gap={1} alignItems={'center'}>
|
||||
{pickCust?.firstname && (
|
||||
<Typography variant="caption" sx={{ color: 'success.main', fontWeight: 600 }}>
|
||||
✓ Selected
|
||||
</Typography>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
variant={pickupSearchOpen ? 'outlined' : 'contained'}
|
||||
size="small"
|
||||
disabled={!locationid}
|
||||
onClick={() => {
|
||||
@@ -1061,19 +1292,115 @@ const Createorder1 = () => {
|
||||
} else if (!appId) {
|
||||
OpenToast('Please select Location!', 'warning', 3000);
|
||||
} else {
|
||||
setIsCustomerOpen(true);
|
||||
setpickordrop(1);
|
||||
setPickCust(null);
|
||||
setInputValue1('');
|
||||
setPickupSearchOpen((prev) => !prev);
|
||||
setDropSearchOpen(false);
|
||||
setSearchCustList('');
|
||||
setpickordrop(1);
|
||||
}
|
||||
}}
|
||||
sx={{ minWidth: 110 }}
|
||||
>
|
||||
Select Pickup
|
||||
{pickupSearchOpen ? 'Close' : 'Select Pickup'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{/* new1 */}
|
||||
|
||||
{/* ============ Inline Pickup Search Panel ============ */}
|
||||
<div
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
maxHeight: pickupSearchOpen ? '380px' : '0px',
|
||||
transition: 'max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
marginTop: pickupSearchOpen ? '12px' : '0px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
border: '1.5px solid #e2e8f0',
|
||||
borderRadius: '12px',
|
||||
background: '#f8fafc',
|
||||
padding: '12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
{/* Search Input */}
|
||||
<OutlinedInput
|
||||
autoFocus={pickupSearchOpen}
|
||||
fullWidth
|
||||
size="small"
|
||||
placeholder="Search customer..."
|
||||
value={pickupSearchOpen ? searchCustList : ''}
|
||||
onChange={(e) => setSearchCustList(e.target.value)}
|
||||
sx={{ bgcolor: 'white', borderRadius: '8px' }}
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
<SearchOutlined style={{ fontSize: 'small' }} />
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={
|
||||
searchCustList ? (
|
||||
<IconButton size="small" onClick={() => setSearchCustList('')}>
|
||||
<ClearIcon fontSize="small" />
|
||||
</IconButton>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Customer List */}
|
||||
<div
|
||||
style={{
|
||||
maxHeight: '280px',
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '6px'
|
||||
}}
|
||||
>
|
||||
{customerlist?.length === 0 ? (
|
||||
<Stack alignItems="center" justifyContent="center" sx={{ py: 2 }}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
) : (
|
||||
customerlist?.map((address, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
disabled={pickCust?.customerid === address.customerid}
|
||||
onClick={() => {
|
||||
setPickupSearchOpen(false);
|
||||
setAddId1(1);
|
||||
setStartPoint({ latitude: address.latitude, longitude: address.longitude });
|
||||
setPickCust(address);
|
||||
setPickNum(address.contactno);
|
||||
setSearchCustList('');
|
||||
}}
|
||||
sx={{
|
||||
width: '100%',
|
||||
border: '1.5px solid',
|
||||
borderColor: pickCust?.customerid === address.customerid ? 'primary.main' : 'grey.200',
|
||||
borderRadius: '8px',
|
||||
p: 1.25,
|
||||
textAlign: 'left',
|
||||
justifyContent: 'flex-start',
|
||||
bgcolor: pickCust?.customerid === address.customerid ? 'primary.lighter' : 'white',
|
||||
'&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(24,144,255,0.04)' }
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Typography variant="subtitle2" sx={{ textAlign: 'left', fontWeight: 600 }}>
|
||||
{`${address.firstname} (${address.contactno})`}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="secondary" sx={{ textAlign: 'left', display: 'block' }}>
|
||||
{address.address}
|
||||
</Typography>
|
||||
</div>
|
||||
</Button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sx={{}}>
|
||||
@@ -1322,12 +1649,18 @@ const Createorder1 = () => {
|
||||
<MainCard sx={{ height: '100%' }}>
|
||||
<Grid container spacing={2} sx={{ height: '100%' }}>
|
||||
<Grid item xs={12} sx={{ height: '100%' }}>
|
||||
<Stack direction={'row'} justifyContent={'space-between'}>
|
||||
<Typography variant="h5" sx={{ mb: 2 }}>
|
||||
<Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'}>
|
||||
<Typography variant="h5" sx={{ mb: 0 }}>
|
||||
Drop Details
|
||||
</Typography>{' '}
|
||||
</Typography>
|
||||
<Stack direction={'row'} gap={1} alignItems={'center'}>
|
||||
{dropCust?.firstname && (
|
||||
<Typography variant="caption" sx={{ color: 'success.main', fontWeight: 600 }}>
|
||||
✓ Selected
|
||||
</Typography>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
variant={dropSearchOpen ? 'outlined' : 'contained'}
|
||||
disabled={!locationid}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
@@ -1338,15 +1671,115 @@ const Createorder1 = () => {
|
||||
} else if (!appId) {
|
||||
OpenToast('Please select Location!', 'warning', 3000);
|
||||
} else {
|
||||
setIsCustomerOpen(true);
|
||||
setDropSearchOpen((prev) => !prev);
|
||||
setPickupSearchOpen(false);
|
||||
setSearchCustList('');
|
||||
setpickordrop(2);
|
||||
setInputValue2('');
|
||||
}
|
||||
}}
|
||||
sx={{ minWidth: 100 }}
|
||||
>
|
||||
Select Drop
|
||||
{dropSearchOpen ? 'Close' : 'Select Drop'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* ============ Inline Drop Search Panel ============ */}
|
||||
<div
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
maxHeight: dropSearchOpen ? '380px' : '0px',
|
||||
transition: 'max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
marginTop: dropSearchOpen ? '12px' : '0px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
border: '1.5px solid #e2e8f0',
|
||||
borderRadius: '12px',
|
||||
background: '#f8fafc',
|
||||
padding: '12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
{/* Search Input */}
|
||||
<OutlinedInput
|
||||
autoFocus={dropSearchOpen}
|
||||
fullWidth
|
||||
size="small"
|
||||
placeholder="Search customer..."
|
||||
value={dropSearchOpen ? searchCustList : ''}
|
||||
onChange={(e) => setSearchCustList(e.target.value)}
|
||||
sx={{ bgcolor: 'white', borderRadius: '8px' }}
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
<SearchOutlined style={{ fontSize: 'small' }} />
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={
|
||||
searchCustList ? (
|
||||
<IconButton size="small" onClick={() => setSearchCustList('')}>
|
||||
<ClearIcon fontSize="small" />
|
||||
</IconButton>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Customer List */}
|
||||
<div
|
||||
style={{
|
||||
maxHeight: '280px',
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '6px'
|
||||
}}
|
||||
>
|
||||
{customerlist?.length === 0 ? (
|
||||
<Stack alignItems="center" justifyContent="center" sx={{ py: 2 }}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
) : (
|
||||
customerlist?.map((address, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
disabled={pickCust?.customerid === address.customerid}
|
||||
onClick={() => {
|
||||
setDropSearchOpen(false);
|
||||
setAddId2(1);
|
||||
setEndPoint({ latitude: address.latitude, longitude: address.longitude });
|
||||
setDropCust(address);
|
||||
setdropNum(address.contactno);
|
||||
setSearchCustList('');
|
||||
}}
|
||||
sx={{
|
||||
width: '100%',
|
||||
border: '1.5px solid',
|
||||
borderColor: dropCust?.customerid === address.customerid ? 'primary.main' : 'grey.200',
|
||||
borderRadius: '8px',
|
||||
p: 1.25,
|
||||
textAlign: 'left',
|
||||
justifyContent: 'flex-start',
|
||||
bgcolor: dropCust?.customerid === address.customerid ? 'primary.lighter' : 'white',
|
||||
'&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(24,144,255,0.04)' }
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Typography variant="subtitle2" sx={{ textAlign: 'left', fontWeight: 600 }}>
|
||||
{`${address.firstname} (${address.contactno})`}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="secondary" sx={{ textAlign: 'left', display: 'block' }}>
|
||||
{address.address}
|
||||
</Typography>
|
||||
</div>
|
||||
</Button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
@@ -1589,6 +2022,22 @@ const Createorder1 = () => {
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* ================================================= || Live Map Preview || ================================================= */}
|
||||
<Grid item xs={12} md={4}>
|
||||
<MainCard sx={{ height: '100%', minHeight: '400px', display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography variant="h5" sx={{ mb: 2 }}>
|
||||
Live Route Preview
|
||||
</Typography>
|
||||
<div style={{ flex: 1, minHeight: '380px', position: 'relative', borderRadius: '8px' }}>
|
||||
<OrderMap startPoint={startPoint} endPoint={endPoint} appLocaLat={appLocaLat} appLocaLng={appLocaLng} />
|
||||
</div>
|
||||
</MainCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* ================================================= || Category || ================================================= */}
|
||||
|
||||
<Grid item xs={12} sx={{}}>
|
||||
@@ -2020,133 +2469,7 @@ const Createorder1 = () => {
|
||||
{/* </Box> */}
|
||||
</Grid>
|
||||
|
||||
{/* ============================================= || saved address Dialog (Customer List)|| ============================================= */}
|
||||
<Dialog
|
||||
open={isCustomerOpen}
|
||||
onClose={() => {
|
||||
setIsCustomerOpen(false);
|
||||
setSearchCustList('');
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ minWidth: 'lg' }}
|
||||
>
|
||||
<DialogTitle sx={{ bgcolor: theme.palette.primary.main, color: 'white' }}>
|
||||
<Stack>
|
||||
<Typography variant="h4"> {`Select Address (${pickordrop === 1 ? 'Pickup' : 'Drop'})`}</Typography>
|
||||
<FormControl
|
||||
sx={{
|
||||
width: '100%',
|
||||
mt: 1
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2} sx={{ py: 0.2 }}>
|
||||
<OutlinedInput
|
||||
autoFocus
|
||||
fullWidth
|
||||
id="input-search-header"
|
||||
placeholder="Search"
|
||||
value={searchCustList}
|
||||
onChange={(e) => setSearchCustList(e.target.value)}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-input': {
|
||||
p: '10.5px 0px 12px'
|
||||
},
|
||||
bgcolor: 'white'
|
||||
}}
|
||||
startAdornment={
|
||||
<InputAdornment position="start">
|
||||
<SearchOutlined style={{ fontSize: 'small' }} />
|
||||
</InputAdornment>
|
||||
}
|
||||
endAdornment={
|
||||
<IconButton
|
||||
sx={{ visibility: searchCustList ? 'visible' : 'hidden' }}
|
||||
onClick={() => {
|
||||
setSearchCustList('');
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</DialogTitle>
|
||||
<Divider />
|
||||
<DialogContent sx={{ p: 2.5 }}>
|
||||
{customerlist?.length == 0 ? (
|
||||
<Stack spacing={2} direction={'row'} alignItems={'center'} justifyContent={'center'} sx={{}}>
|
||||
<Empty />
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack spacing={2} sx={{}}>
|
||||
{customerlist &&
|
||||
customerlist?.map((address, index) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsCustomerOpen(false);
|
||||
|
||||
if (pickordrop === 1) {
|
||||
console.log('PickupClient', address);
|
||||
setAddId1(1);
|
||||
setStartPoint({ latitude: address.latitude, longitude: address.longitude });
|
||||
setPickCust(address);
|
||||
setPickNum(address.contactno);
|
||||
} else {
|
||||
console.log('DropClient', address);
|
||||
setAddId2(1);
|
||||
setEndPoint({ latitude: address.latitude, longitude: address.longitude });
|
||||
setDropCust(address);
|
||||
setdropNum(address.contactno);
|
||||
}
|
||||
setSearchCustList('');
|
||||
}}
|
||||
disabled={pickCust?.customerid === address.customerid}
|
||||
key={index}
|
||||
sx={{
|
||||
width: '100%',
|
||||
border: '1px solid',
|
||||
borderColor: 'grey.200',
|
||||
borderRadius: 1,
|
||||
p: 1.25
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Typography variant="subtitle1" sx={{ textAlign: 'left' }}>
|
||||
{`${address.firstname} (${address.contactno})`}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="secondary" sx={{ textAlign: 'left' }}>
|
||||
{address.address}
|
||||
</Typography>
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContent>
|
||||
<Divider />
|
||||
<DialogActions sx={{ p: 2.5 }}>
|
||||
<Button
|
||||
color="error"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
'&:hover': {
|
||||
bgcolor: 'red',
|
||||
color: 'white'
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
setIsCustomerOpen(false);
|
||||
setSearchCustList('');
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
{/* Customer Dialog removed — replaced with inline search panels above */}
|
||||
{/* ============================================= || location error Dialog || ============================================= */}
|
||||
<Dialog
|
||||
open={open}
|
||||
|
||||
66
src/utils/logger.js
Normal file
66
src/utils/logger.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const LOG_LEVELS = {
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
};
|
||||
|
||||
// Default log level based on environment
|
||||
const currentEnv = process.env.NODE_ENV || 'development';
|
||||
const isDev = currentEnv === 'development';
|
||||
const GLOBAL_LOG_LEVEL = isDev ? LOG_LEVELS.DEBUG : LOG_LEVELS.WARN;
|
||||
|
||||
const style = (bg, color) => `background: ${bg}; color: ${color}; padding: 2px 5px; border-radius: 4px; font-weight: bold;`;
|
||||
|
||||
const PREFIX = '%c[NearlExpress]';
|
||||
const PREFIX_STYLE = style('#2563eb', '#ffffff');
|
||||
|
||||
// Capture original console methods before any global overrides occur
|
||||
const originalLog = console.log;
|
||||
const originalWarn = console.warn || console.log;
|
||||
const originalError = console.error || console.log;
|
||||
|
||||
const print = (levelName, args, labelStyle) => {
|
||||
const levelValue = LOG_LEVELS[levelName];
|
||||
if (levelValue < GLOBAL_LOG_LEVEL) return;
|
||||
|
||||
const [message, ...extra] = args;
|
||||
const isMessageString = typeof message === 'string';
|
||||
|
||||
const formatPrefix = `${PREFIX}%c ${levelName}`;
|
||||
const styles = [PREFIX_STYLE, labelStyle];
|
||||
|
||||
const consoleMethod =
|
||||
levelName === 'ERROR' ? originalError :
|
||||
levelName === 'WARN' ? originalWarn :
|
||||
originalLog;
|
||||
|
||||
if (isMessageString) {
|
||||
consoleMethod(
|
||||
`${formatPrefix}%c ${message}`,
|
||||
...styles,
|
||||
'color: inherit;',
|
||||
...extra
|
||||
);
|
||||
} else {
|
||||
// If first argument is an object/array, preserve raw interactive log
|
||||
consoleMethod(
|
||||
`${formatPrefix}`,
|
||||
...styles,
|
||||
message,
|
||||
...extra
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const logger = {
|
||||
debug: (...args) => print('DEBUG', args, style('#64748b', '#ffffff')),
|
||||
info: (...args) => print('INFO', args, style('#10b981', '#ffffff')),
|
||||
warn: (...args) => print('WARN', args, style('#f59e0b', '#ffffff')),
|
||||
error: (...args) => {
|
||||
print('ERROR', args, style('#ef4444', '#ffffff'));
|
||||
// Future expansion hook: e.g., Sentry.captureException(args[0]);
|
||||
},
|
||||
};
|
||||
|
||||
export default logger;
|
||||
Reference in New Issue
Block a user