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 { createRoot } from 'react-dom/client';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -38,6 +39,8 @@ if (process.env.NODE_ENV !== 'development') {
|
|||||||
console.warn = () => {}; // Optionally disable console.warn
|
console.warn = () => {}; // Optionally disable console.warn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info('NearlExpress console application starting...');
|
||||||
|
|
||||||
// const root = ReactDOM.createRoot(document.getElementById('root'));
|
// const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
|
||||||
// ==============================|| MAIN - REACT DOM RENDER ||============================== //
|
// ==============================|| MAIN - REACT DOM RENDER ||============================== //
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
// material-ui
|
// material-ui
|
||||||
@@ -22,6 +23,7 @@ const MainLayout = () => {
|
|||||||
const matchDownXL = useMediaQuery(theme.breakpoints.down('xl'));
|
const matchDownXL = useMediaQuery(theme.breakpoints.down('xl'));
|
||||||
const downLG = useMediaQuery(theme.breakpoints.down('lg'));
|
const downLG = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
|
|
||||||
|
const { drawerOpen } = useSelector((state) => state.menu);
|
||||||
const { container, miniDrawer, menuOrientation } = useConfig();
|
const { container, miniDrawer, menuOrientation } = useConfig();
|
||||||
|
|
||||||
const isHorizontal = menuOrientation === MenuOrientation.HORIZONTAL && !downLG;
|
const isHorizontal = menuOrientation === MenuOrientation.HORIZONTAL && !downLG;
|
||||||
@@ -39,7 +41,14 @@ const MainLayout = () => {
|
|||||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
<Box sx={{ display: 'flex', width: '100%' }}>
|
||||||
<Header />
|
<Header />
|
||||||
{!isHorizontal ? <Drawer /> : <HorizontalBar />}
|
{!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' }} />
|
<Toolbar sx={{ mt: isHorizontal ? 8 : 'inherit' }} />
|
||||||
<Container
|
<Container
|
||||||
// maxWidth={container ? 'xl' : false}
|
// maxWidth={container ? 'xl' : false}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logger from '../../../utils/logger';
|
||||||
import { enqueueSnackbar } from 'notistack';
|
import { enqueueSnackbar } from 'notistack';
|
||||||
import { DeleteFilled, EditOutlined } from '@ant-design/icons';
|
import { DeleteFilled, EditOutlined } from '@ant-design/icons';
|
||||||
import { useState, useEffect, Fragment, useRef } from 'react';
|
import { useState, useEffect, Fragment, useRef } from 'react';
|
||||||
@@ -258,18 +259,19 @@ const Deliveries = () => {
|
|||||||
// =========================================== || changerider || ===========================================
|
// =========================================== || changerider || ===========================================
|
||||||
const changeRiderMutation = useMutation({
|
const changeRiderMutation = useMutation({
|
||||||
mutationFn: ({ selectedRider, selectedRow }) => changeRiderAPI(selectedRider, selectedRow),
|
mutationFn: ({ selectedRider, selectedRow }) => changeRiderAPI(selectedRider, selectedRow),
|
||||||
onSuccess: (res) => {
|
onSuccess: (res, { selectedRider, selectedRow }) => {
|
||||||
setLoading1(false);
|
setLoading1(false);
|
||||||
setChangeDialogOpen(false);
|
setChangeDialogOpen(false);
|
||||||
if (res.data.message === 'Success') {
|
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');
|
opentoast('Rider Changed Successfully', 'success');
|
||||||
}
|
}
|
||||||
fetchCountRefetch(); // Refresh count data
|
fetchCountRefetch(); // Refresh count data
|
||||||
fetchDeliveriesRefetch(); // Refresh deliveries
|
fetchDeliveriesRefetch(); // Refresh deliveries
|
||||||
notifyRiderMutation.mutate(selectedRider.userfcmtoken);
|
notifyRiderMutation.mutate(selectedRider.userfcmtoken);
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err, { selectedRider, selectedRow }) => {
|
||||||
console.log(err);
|
logger.error(`Failed to change rider for order ID ${selectedRow?.orderid}:`, err);
|
||||||
opentoast(err.message, 'error');
|
opentoast(err.message, 'error');
|
||||||
setLoading1(false);
|
setLoading1(false);
|
||||||
}
|
}
|
||||||
@@ -1236,7 +1238,7 @@ const Deliveries = () => {
|
|||||||
Notify Rider
|
Notify Rider
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{['pending', 'accepted', 'arrived'].includes(selectedRow?.orderstatus) && (
|
{/* {['pending', 'accepted', 'arrived'].includes(selectedRow?.orderstatus) && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
@@ -1250,7 +1252,7 @@ const Deliveries = () => {
|
|||||||
>
|
>
|
||||||
Change Rider
|
Change Rider
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)} */}
|
||||||
{(roleid == 1 || roleid == 2) && (
|
{(roleid == 1 || roleid == 2) && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1487,7 +1489,7 @@ const Deliveries = () => {
|
|||||||
renderInput={(params) => <TextField {...params} label="Choose Rider" />}
|
renderInput={(params) => <TextField {...params} label="Choose Rider" />}
|
||||||
onChange={(e, value) => {
|
onChange={(e, value) => {
|
||||||
setSelectedRider(value);
|
setSelectedRider(value);
|
||||||
console.log('selected rider', value);
|
logger.debug('Rider selected in dropdown:', value ? `${value.firstname} ${value.lastname}` : 'None');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1509,6 +1511,7 @@ const Deliveries = () => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setLoading1(true);
|
setLoading1(true);
|
||||||
|
logger.info(`Initiating rider assignment change for order ID ${selectedRow?.orderid} to rider: ${selectedRider?.firstname} ${selectedRider?.lastname}`);
|
||||||
changeRiderMutation.mutate({ selectedRider, selectedRow });
|
changeRiderMutation.mutate({ selectedRider, selectedRow });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,7 @@ import {
|
|||||||
} from './dispatchShared';
|
} from './dispatchShared';
|
||||||
import CompareDataPanel from './CompareDataPanel';
|
import CompareDataPanel from './CompareDataPanel';
|
||||||
import './Dispatch.css';
|
import './Dispatch.css';
|
||||||
|
import logger from '../../../utils/logger';
|
||||||
|
|
||||||
// Combined-mode rail colors. The per-step palette (STEP_PALETTE) is great for
|
// 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
|
// "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
|
// marker; clicking the marker again unpins it. Stored in a ref because this
|
||||||
// only drives imperative leaflet calls — no re-render needed.
|
// only drives imperative leaflet calls — no re-render needed.
|
||||||
const pinnedPopupsRef = useRef(new Set());
|
const pinnedPopupsRef = useRef(new Set());
|
||||||
// Short-lived close timer for the Compare drop-pin popup. Gives the
|
// Short-lived close timer for the general map order/marker popups.
|
||||||
// cursor a ~120ms window to travel from the marker onto the popup
|
// Gives the cursor a ~200ms window to travel from the marker onto the popup
|
||||||
// without firing mouseout → closePopup mid-transit. Any new mouseover
|
// or vice versa without immediately triggering a close.
|
||||||
// cancels a pending close, so the popup glides instead of flickering.
|
const activePopupMarkerRef = useRef(null);
|
||||||
const compareHoverTimerRef = useRef(null);
|
const popupHoverTimerRef = useRef(null);
|
||||||
const isControlled = selectedRiderId !== undefined;
|
const isControlled = selectedRiderId !== undefined;
|
||||||
const [clock, setClock] = useState('');
|
const [clock, setClock] = useState('');
|
||||||
|
|
||||||
@@ -898,6 +899,7 @@ const Dispatch = ({
|
|||||||
}, [appLocations, selectedAppLocationId]);
|
}, [appLocations, selectedAppLocationId]);
|
||||||
|
|
||||||
const handleLocationPick = (id) => {
|
const handleLocationPick = (id) => {
|
||||||
|
logger.info('Switching hub/location ID:', id);
|
||||||
setSelectedAppLocationId(Number(id));
|
setSelectedAppLocationId(Number(id));
|
||||||
setLocationMenuOpen(false);
|
setLocationMenuOpen(false);
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -1383,6 +1385,53 @@ const Dispatch = ({
|
|||||||
[isControlled, onRiderSelect]
|
[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(() => {
|
const activeStats = useMemo(() => {
|
||||||
if (focusedRider) {
|
if (focusedRider) {
|
||||||
return {
|
return {
|
||||||
@@ -2127,8 +2176,44 @@ const Dispatch = ({
|
|||||||
// chips — operators learn the layout once and trust it everywhere.
|
// chips — operators learn the layout once and trust it everywhere.
|
||||||
const renderOrderPopupContent = (o) => {
|
const renderOrderPopupContent = (o) => {
|
||||||
const statusStyle = getStatusStyle(o.orderstatus);
|
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 (
|
return (
|
||||||
<>
|
<div
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
>
|
||||||
<div className="pu-header">
|
<div className="pu-header">
|
||||||
<div className="pu-header-top">
|
<div className="pu-header-top">
|
||||||
<div className="pu-id">ORDER #{o.orderid}</div>
|
<div className="pu-id">ORDER #{o.orderid}</div>
|
||||||
@@ -2234,7 +2319,7 @@ const Dispatch = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2318,11 +2403,29 @@ const Dispatch = ({
|
|||||||
else delete orderMarkerRefs.current[String(o.orderid)];
|
else delete orderMarkerRefs.current[String(o.orderid)];
|
||||||
}}
|
}}
|
||||||
eventHandlers={{
|
eventHandlers={{
|
||||||
mouseover: (e) => e.target.openPopup(),
|
mouseover: (e) => {
|
||||||
mouseout: (e) => {
|
const marker = e.target;
|
||||||
if (!pinnedPopupsRef.current.has(String(o.orderid))) {
|
if (popupHoverTimerRef.current) {
|
||||||
e.target.closePopup();
|
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) => {
|
click: (e) => {
|
||||||
const id = String(o.orderid);
|
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)}
|
{renderOrderPopupContent(o)}
|
||||||
</Popup>
|
</Popup>
|
||||||
</Marker>
|
</Marker>
|
||||||
@@ -2836,13 +2939,13 @@ const Dispatch = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div id="strat-row">
|
<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
|
<button
|
||||||
className={`sbt ${viewMode === 'zones' ? 'active' : ''}`}
|
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>
|
><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 === '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={() => { setViewMode('all'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdPublic /></span> All Routes</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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`sbt sbt-rider-info ${viewMode === 'rider-info' ? 'active' : ''}`}
|
className={`sbt sbt-rider-info ${viewMode === 'rider-info' ? 'active' : ''}`}
|
||||||
@@ -3886,7 +3989,7 @@ const Dispatch = ({
|
|||||||
mouseout: (e) => e.target.closePopup()
|
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-header">KITCHEN</div>
|
||||||
<div className="kp-name">{k.kitchenName}</div>
|
<div className="kp-name">{k.kitchenName}</div>
|
||||||
<div className="kp-stat">
|
<div className="kp-stat">
|
||||||
@@ -3931,7 +4034,7 @@ const Dispatch = ({
|
|||||||
mouseout: (e) => e.target.closePopup()
|
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-live">
|
||||||
<div className="pu-hdr-left">
|
<div className="pu-hdr-left">
|
||||||
<span className="pu-hdr-title">RIDER ROUTE</span>
|
<span className="pu-hdr-title">RIDER ROUTE</span>
|
||||||
@@ -4011,7 +4114,7 @@ const Dispatch = ({
|
|||||||
mouseout: (e) => e.target.closePopup()
|
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-live">
|
||||||
<div className="pu-hdr-left">
|
<div className="pu-hdr-left">
|
||||||
<span className="pu-live-indicator" style={{ '--pulse-color': pinColor }}>
|
<span className="pu-live-indicator" style={{ '--pulse-color': pinColor }}>
|
||||||
@@ -4272,25 +4375,30 @@ const Dispatch = ({
|
|||||||
// the rich order popup, leaving it pinned while
|
// the rich order popup, leaving it pinned while
|
||||||
// a step is focused (so click-to-focus keeps the
|
// a step is focused (so click-to-focus keeps the
|
||||||
// modal visible after the cursor moves away). 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.
|
// travel onto the popup itself without flicker.
|
||||||
mouseover: (e) => {
|
mouseover: (e) => {
|
||||||
if (compareHoverTimerRef.current) {
|
const marker = e.target;
|
||||||
clearTimeout(compareHoverTimerRef.current);
|
if (popupHoverTimerRef.current) {
|
||||||
compareHoverTimerRef.current = null;
|
clearTimeout(popupHoverTimerRef.current);
|
||||||
|
popupHoverTimerRef.current = null;
|
||||||
}
|
}
|
||||||
e.target.openPopup();
|
activePopupMarkerRef.current = marker;
|
||||||
|
marker.openPopup();
|
||||||
},
|
},
|
||||||
mouseout: (e) => {
|
mouseout: (e) => {
|
||||||
if (focusedCompareStep === t.sequenceStep) return;
|
if (focusedCompareStep === t.sequenceStep) return;
|
||||||
const marker = e.target;
|
const marker = e.target;
|
||||||
if (compareHoverTimerRef.current) {
|
if (popupHoverTimerRef.current) {
|
||||||
clearTimeout(compareHoverTimerRef.current);
|
clearTimeout(popupHoverTimerRef.current);
|
||||||
}
|
}
|
||||||
compareHoverTimerRef.current = setTimeout(() => {
|
popupHoverTimerRef.current = setTimeout(() => {
|
||||||
marker.closePopup();
|
marker.closePopup();
|
||||||
compareHoverTimerRef.current = null;
|
if (activePopupMarkerRef.current === marker) {
|
||||||
}, 120);
|
activePopupMarkerRef.current = null;
|
||||||
|
}
|
||||||
|
popupHoverTimerRef.current = null;
|
||||||
|
}, 200);
|
||||||
},
|
},
|
||||||
click: handleEndMarkerClick
|
click: handleEndMarkerClick
|
||||||
}
|
}
|
||||||
@@ -4349,7 +4457,8 @@ const Dispatch = ({
|
|||||||
maxWidth={520}
|
maxWidth={520}
|
||||||
minWidth={460}
|
minWidth={460}
|
||||||
className="dispatch-popup"
|
className="dispatch-popup"
|
||||||
autoPan={false}
|
autoPan={true}
|
||||||
|
autoPanPadding={[40, 40]}
|
||||||
>
|
>
|
||||||
{renderOrderPopupContent(orderForTrack)}
|
{renderOrderPopupContent(orderForTrack)}
|
||||||
</Popup>
|
</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 ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
|
||||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||||
import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
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) {
|
function loadScript(src, position, id) {
|
||||||
if (!position) {
|
if (!position) {
|
||||||
@@ -145,6 +365,8 @@ const Createorder1 = () => {
|
|||||||
const [pickDialog, setPickDialog] = useState(false);
|
const [pickDialog, setPickDialog] = useState(false);
|
||||||
const [deliverydate, setDeliverydate] = useState(dayjs().add(30, 'minute'));
|
const [deliverydate, setDeliverydate] = useState(dayjs().add(30, 'minute'));
|
||||||
const [deliveryDialog, setDeliveryDialog] = useState(false);
|
const [deliveryDialog, setDeliveryDialog] = useState(false);
|
||||||
|
const [pickupSearchOpen, setPickupSearchOpen] = useState(false);
|
||||||
|
const [dropSearchOpen, setDropSearchOpen] = useState(false);
|
||||||
|
|
||||||
const pickerRef = useRef(null);
|
const pickerRef = useRef(null);
|
||||||
const deliveryRef = useRef(null);
|
const deliveryRef = useRef(null);
|
||||||
@@ -319,7 +541,7 @@ const Createorder1 = () => {
|
|||||||
if (tenantid) {
|
if (tenantid) {
|
||||||
clientdetails();
|
clientdetails();
|
||||||
}
|
}
|
||||||
}, [searchCustList?.length > 3, searchCustList == '', tenantid]);
|
}, [searchCustList?.length > 3, searchCustList == '', tenantid, pickupSearchOpen, dropSearchOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timeslotarr[0]) {
|
if (timeslotarr[0]) {
|
||||||
@@ -1037,6 +1259,10 @@ const Createorder1 = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
<Grid item xs={12}>
|
<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%' }}>
|
<Grid container spacing={2} sx={{ height: '100%' }}>
|
||||||
{/* ================================================= || Pickup || ================================================= */}
|
{/* ================================================= || Pickup || ================================================= */}
|
||||||
|
|
||||||
@@ -1044,13 +1270,18 @@ const Createorder1 = () => {
|
|||||||
<MainCard sx={{ height: '100%' }}>
|
<MainCard sx={{ height: '100%' }}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Stack direction={'row'} justifyContent={'space-between'}>
|
<Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'}>
|
||||||
<Typography variant="h5" sx={{ mb: 2 }}>
|
<Typography variant="h5" sx={{ mb: 0 }}>
|
||||||
Pickup Details
|
Pickup Details
|
||||||
</Typography>
|
</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
|
<Button
|
||||||
variant="contained"
|
variant={pickupSearchOpen ? 'outlined' : 'contained'}
|
||||||
size="small"
|
size="small"
|
||||||
disabled={!locationid}
|
disabled={!locationid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1061,19 +1292,115 @@ const Createorder1 = () => {
|
|||||||
} else if (!appId) {
|
} else if (!appId) {
|
||||||
OpenToast('Please select Location!', 'warning', 3000);
|
OpenToast('Please select Location!', 'warning', 3000);
|
||||||
} else {
|
} else {
|
||||||
setIsCustomerOpen(true);
|
setPickupSearchOpen((prev) => !prev);
|
||||||
setpickordrop(1);
|
setDropSearchOpen(false);
|
||||||
setPickCust(null);
|
|
||||||
setInputValue1('');
|
|
||||||
setSearchCustList('');
|
setSearchCustList('');
|
||||||
|
setpickordrop(1);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
sx={{ minWidth: 110 }}
|
||||||
>
|
>
|
||||||
Select Pickup
|
{pickupSearchOpen ? 'Close' : 'Select Pickup'}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</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>
|
||||||
|
|
||||||
<Grid item xs={12} sx={{}}>
|
<Grid item xs={12} sx={{}}>
|
||||||
@@ -1322,12 +1649,18 @@ const Createorder1 = () => {
|
|||||||
<MainCard sx={{ height: '100%' }}>
|
<MainCard sx={{ height: '100%' }}>
|
||||||
<Grid container spacing={2} sx={{ height: '100%' }}>
|
<Grid container spacing={2} sx={{ height: '100%' }}>
|
||||||
<Grid item xs={12} sx={{ height: '100%' }}>
|
<Grid item xs={12} sx={{ height: '100%' }}>
|
||||||
<Stack direction={'row'} justifyContent={'space-between'}>
|
<Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'}>
|
||||||
<Typography variant="h5" sx={{ mb: 2 }}>
|
<Typography variant="h5" sx={{ mb: 0 }}>
|
||||||
Drop Details
|
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
|
<Button
|
||||||
variant="contained"
|
variant={dropSearchOpen ? 'outlined' : 'contained'}
|
||||||
disabled={!locationid}
|
disabled={!locationid}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1338,15 +1671,115 @@ const Createorder1 = () => {
|
|||||||
} else if (!appId) {
|
} else if (!appId) {
|
||||||
OpenToast('Please select Location!', 'warning', 3000);
|
OpenToast('Please select Location!', 'warning', 3000);
|
||||||
} else {
|
} else {
|
||||||
setIsCustomerOpen(true);
|
setDropSearchOpen((prev) => !prev);
|
||||||
|
setPickupSearchOpen(false);
|
||||||
|
setSearchCustList('');
|
||||||
setpickordrop(2);
|
setpickordrop(2);
|
||||||
setInputValue2('');
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
sx={{ minWidth: 100 }}
|
||||||
>
|
>
|
||||||
Select Drop
|
{dropSearchOpen ? 'Close' : 'Select Drop'}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</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>
|
||||||
|
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
@@ -1589,6 +2022,22 @@ const Createorder1 = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</MainCard>
|
</MainCard>
|
||||||
</Grid>
|
</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 || ================================================= */}
|
{/* ================================================= || Category || ================================================= */}
|
||||||
|
|
||||||
<Grid item xs={12} sx={{}}>
|
<Grid item xs={12} sx={{}}>
|
||||||
@@ -2020,133 +2469,7 @@ const Createorder1 = () => {
|
|||||||
{/* </Box> */}
|
{/* </Box> */}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* ============================================= || saved address Dialog (Customer List)|| ============================================= */}
|
{/* Customer Dialog removed — replaced with inline search panels above */}
|
||||||
<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>
|
|
||||||
{/* ============================================= || location error Dialog || ============================================= */}
|
{/* ============================================= || location error Dialog || ============================================= */}
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
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