diff --git a/src/pages/nearle/dispatch/ActiveSection.js b/src/pages/nearle/dispatch/ActiveSection.js
index 6976465..aab6c32 100644
--- a/src/pages/nearle/dispatch/ActiveSection.js
+++ b/src/pages/nearle/dispatch/ActiveSection.js
@@ -7,6 +7,7 @@ import {
MdMyLocation,
MdInventory2
} from 'react-icons/md';
+import dayjs from 'dayjs';
import { getStatusStyle, getActiveOrder } from './dispatchShared';
const ActiveSection = ({
@@ -83,13 +84,20 @@ const ActiveSection = ({
{riderName}
-
- {statusStyle.label}
-
+
+
+ {statusStyle.label}
+
+ {o.deliverytime && (
+
+ {dayjs(o.deliverytime).isValid() ? dayjs(o.deliverytime).format('HH:mm:ss') : String(o.deliverytime)}
+
+ )}
+
{dropArea && (
diff --git a/src/pages/nearle/dispatch/Dispatch.js b/src/pages/nearle/dispatch/Dispatch.js
index 9bff09b..35e9bad 100644
--- a/src/pages/nearle/dispatch/Dispatch.js
+++ b/src/pages/nearle/dispatch/Dispatch.js
@@ -800,8 +800,46 @@ const MapController = ({ focusedItem, viewMode, orders, kitchens, locationKey })
// automatically. Either way motion is smooth and never freezes mid-trip.
// `duration` is only the seed used for the very first segment (no prior fix to
// measure against yet).
-const MIN_GLIDE_MS = 800; // floor: don't animate faster than this even on rapid fixes
-const MAX_GLIDE_MS = 32_000; // ceiling: cover the ~30s backend cadence + small buffer
+const MIN_GLIDE_MS = 200; // floor: don't animate faster than this even on rapid fixes
+const MAX_GLIDE_MS = 2500; // ceiling: tighter ceiling for 1s update cadence
+const interpolatePath = (path, t) => {
+ if (!path || path.length === 0) return null;
+ if (path.length === 1) return path[0];
+ if (t <= 0) return path[0];
+ if (t >= 1) return path[path.length - 1];
+
+ const segmentDistances = [];
+ let totalDistance = 0;
+ for (let i = 0; i < path.length - 1; i++) {
+ const p1 = path[i];
+ const p2 = path[i + 1];
+ const dLat = p2[0] - p1[0];
+ const dLng = p2[1] - p1[1];
+ const dist = Math.sqrt(dLat * dLat + dLng * dLng);
+ segmentDistances.push(dist);
+ totalDistance += dist;
+ }
+
+ if (totalDistance === 0) return path[0];
+
+ const targetDistance = t * totalDistance;
+ let accumulatedDistance = 0;
+ for (let i = 0; i < segmentDistances.length; i++) {
+ const nextDistance = accumulatedDistance + segmentDistances[i];
+ if (nextDistance >= targetDistance) {
+ const segT = (targetDistance - accumulatedDistance) / segmentDistances[i];
+ const p1 = path[i];
+ const p2 = path[i + 1];
+ return [
+ p1[0] + (p2[0] - p1[0]) * segT,
+ p1[1] + (p2[1] - p1[1]) * segT
+ ];
+ }
+ accumulatedDistance = nextDistance;
+ }
+ return path[path.length - 1];
+};
+
const AnimatedRiderMarker = ({ target, icon, duration = 950, zIndexOffset, eventHandlers, children, markerRef: externalRef }) => {
const markerRef = useRef(null);
const rafRef = useRef(null);
@@ -861,17 +899,46 @@ const AnimatedRiderMarker = ({ target, icon, duration = 950, zIndexOffset, event
prevFixTsRef.current = startTs;
const startLat = from.lat;
const startLng = from.lng;
+
+ let active = true;
+ let osrmPath = null;
+
+ // Fetch OSRM route for this step so the bike follows the roads
+ const fetchOsrmPath = async () => {
+ const url = `https://router.project-osrm.org/route/v1/driving/${from.lng},${from.lat};${to.lng},${to.lat}?overview=full&geometries=geojson`;
+ try {
+ const res = await fetch(url);
+ const json = await res.json();
+ if (active && json.routes && json.routes[0]) {
+ osrmPath = json.routes[0].geometry.coordinates.map(c => [c[1], c[0]]);
+ }
+ } catch (e) {
+ console.error('OSRM animated rider marker fetch error:', e);
+ }
+ };
+ fetchOsrmPath();
+
const step = (now) => {
+ if (!active) return;
const t = Math.min(1, (now - startTs) / segMs);
- // Linear interpolation → constant speed. Spanning the glide across the
- // full inter-fix interval chains consecutive segments into one continuous
- // motion (a vehicle moving down the road) rather than the accelerate/brake
- // feel an easing curve gives, or the dart-then-freeze of a fixed duration.
- marker.setLatLng([startLat + dLat * t, startLng + dLng * t]);
+
+ if (osrmPath && osrmPath.length >= 2) {
+ const pos = interpolatePath(osrmPath, t);
+ if (pos) {
+ marker.setLatLng(pos);
+ } else {
+ marker.setLatLng([startLat + dLat * t, startLng + dLng * t]);
+ }
+ } else {
+ // Fallback to linear interpolation if OSRM route isn't available
+ marker.setLatLng([startLat + dLat * t, startLng + dLng * t]);
+ }
+
if (t < 1) rafRef.current = requestAnimationFrame(step);
};
rafRef.current = requestAnimationFrame(step);
return () => {
+ active = false;
if (rafRef.current) cancelAnimationFrame(rafRef.current);
};
}, [lat, lon, duration]);
@@ -1455,13 +1522,13 @@ const Dispatch = ({
// hub (latitude/longitude/logdate/status). We render those positions as
// markers on the main dispatch map so the operator sees where each rider
// actually is — matching the Reports → Riders Logs page.
- const RIDER_LOG_POLL_MS = 15000;
+ const RIDER_LOG_POLL_MS = 1000;
const { data: ridersLocationLogs } = useQuery({
queryKey: [selectedAppLocationId, selectedDate, ''],
queryFn: fetchRidersLogs,
refetchInterval: RIDER_LOG_POLL_MS,
refetchIntervalInBackground: false,
- staleTime: 5 * 1000,
+ staleTime: 0,
refetchOnWindowFocus: false
});
@@ -4143,7 +4210,7 @@ const Dispatch = ({
*/}
- {(focusedRider || focusedKitchen) ? (
+ {((focusedRider && !isAllActiveView) || focusedKitchen) ? (
{focusedRider ? (
@@ -5028,7 +5095,7 @@ const Dispatch = ({
// existing views behave exactly as they did before.
const LiveMarker = isAllActiveView ? AnimatedRiderMarker : Marker;
const positionProps = isAllActiveView
- ? { target: [r.lat, r.lon], duration: 15000 }
+ ? { target: [r.lat, r.lon], duration: 1000 }
: { position: [r.lat, r.lon] };
return (