updates on the activeSection and dispatch
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
|||||||
MdMyLocation,
|
MdMyLocation,
|
||||||
MdInventory2
|
MdInventory2
|
||||||
} from 'react-icons/md';
|
} from 'react-icons/md';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { getStatusStyle, getActiveOrder } from './dispatchShared';
|
import { getStatusStyle, getActiveOrder } from './dispatchShared';
|
||||||
|
|
||||||
const ActiveSection = ({
|
const ActiveSection = ({
|
||||||
@@ -83,13 +84,20 @@ const ActiveSection = ({
|
|||||||
<span className="adcard-tx">{riderName}</span>
|
<span className="adcard-tx">{riderName}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: '4px' }}>
|
||||||
className="adcard-status"
|
<span
|
||||||
style={{ background: `${statusStyle.bg}1a`, color: statusStyle.bg }}
|
className="adcard-status"
|
||||||
title={statusStyle.label}
|
style={{ background: `${statusStyle.bg}1a`, color: statusStyle.bg }}
|
||||||
>
|
title={statusStyle.label}
|
||||||
{statusStyle.label}
|
>
|
||||||
</span>
|
{statusStyle.label}
|
||||||
|
</span>
|
||||||
|
{o.deliverytime && (
|
||||||
|
<span className="adcard-time" style={{ fontSize: '11px', color: '#64748b' }}>
|
||||||
|
{dayjs(o.deliverytime).isValid() ? dayjs(o.deliverytime).format('HH:mm:ss') : String(o.deliverytime)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{dropArea && (
|
{dropArea && (
|
||||||
|
|||||||
@@ -800,8 +800,46 @@ const MapController = ({ focusedItem, viewMode, orders, kitchens, locationKey })
|
|||||||
// automatically. Either way motion is smooth and never freezes mid-trip.
|
// 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
|
// `duration` is only the seed used for the very first segment (no prior fix to
|
||||||
// measure against yet).
|
// measure against yet).
|
||||||
const MIN_GLIDE_MS = 800; // floor: don't animate faster than this even on rapid fixes
|
const MIN_GLIDE_MS = 200; // 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 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 AnimatedRiderMarker = ({ target, icon, duration = 950, zIndexOffset, eventHandlers, children, markerRef: externalRef }) => {
|
||||||
const markerRef = useRef(null);
|
const markerRef = useRef(null);
|
||||||
const rafRef = useRef(null);
|
const rafRef = useRef(null);
|
||||||
@@ -861,17 +899,46 @@ const AnimatedRiderMarker = ({ target, icon, duration = 950, zIndexOffset, event
|
|||||||
prevFixTsRef.current = startTs;
|
prevFixTsRef.current = startTs;
|
||||||
const startLat = from.lat;
|
const startLat = from.lat;
|
||||||
const startLng = from.lng;
|
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) => {
|
const step = (now) => {
|
||||||
|
if (!active) return;
|
||||||
const t = Math.min(1, (now - startTs) / segMs);
|
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
|
if (osrmPath && osrmPath.length >= 2) {
|
||||||
// motion (a vehicle moving down the road) rather than the accelerate/brake
|
const pos = interpolatePath(osrmPath, t);
|
||||||
// feel an easing curve gives, or the dart-then-freeze of a fixed duration.
|
if (pos) {
|
||||||
marker.setLatLng([startLat + dLat * t, startLng + dLng * t]);
|
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);
|
if (t < 1) rafRef.current = requestAnimationFrame(step);
|
||||||
};
|
};
|
||||||
rafRef.current = requestAnimationFrame(step);
|
rafRef.current = requestAnimationFrame(step);
|
||||||
return () => {
|
return () => {
|
||||||
|
active = false;
|
||||||
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
||||||
};
|
};
|
||||||
}, [lat, lon, duration]);
|
}, [lat, lon, duration]);
|
||||||
@@ -1455,13 +1522,13 @@ const Dispatch = ({
|
|||||||
// hub (latitude/longitude/logdate/status). We render those positions as
|
// hub (latitude/longitude/logdate/status). We render those positions as
|
||||||
// markers on the main dispatch map so the operator sees where each rider
|
// markers on the main dispatch map so the operator sees where each rider
|
||||||
// actually is — matching the Reports → Riders Logs page.
|
// actually is — matching the Reports → Riders Logs page.
|
||||||
const RIDER_LOG_POLL_MS = 15000;
|
const RIDER_LOG_POLL_MS = 1000;
|
||||||
const { data: ridersLocationLogs } = useQuery({
|
const { data: ridersLocationLogs } = useQuery({
|
||||||
queryKey: [selectedAppLocationId, selectedDate, ''],
|
queryKey: [selectedAppLocationId, selectedDate, ''],
|
||||||
queryFn: fetchRidersLogs,
|
queryFn: fetchRidersLogs,
|
||||||
refetchInterval: RIDER_LOG_POLL_MS,
|
refetchInterval: RIDER_LOG_POLL_MS,
|
||||||
refetchIntervalInBackground: false,
|
refetchIntervalInBackground: false,
|
||||||
staleTime: 5 * 1000,
|
staleTime: 0,
|
||||||
refetchOnWindowFocus: false
|
refetchOnWindowFocus: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4143,7 +4210,7 @@ const Dispatch = ({
|
|||||||
</div>
|
</div>
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
{(focusedRider || focusedKitchen) ? (
|
{((focusedRider && !isAllActiveView) || focusedKitchen) ? (
|
||||||
<div id="route-detail">
|
<div id="route-detail">
|
||||||
<button className="rd-back" onClick={() => { handleRiderFocus(null); setFocusedKitchen(null); }}>← Back to {focusedZone ? focusedZone.name : 'list'}</button>
|
<button className="rd-back" onClick={() => { handleRiderFocus(null); setFocusedKitchen(null); }}>← Back to {focusedZone ? focusedZone.name : 'list'}</button>
|
||||||
{focusedRider ? (
|
{focusedRider ? (
|
||||||
@@ -5028,7 +5095,7 @@ const Dispatch = ({
|
|||||||
// existing views behave exactly as they did before.
|
// existing views behave exactly as they did before.
|
||||||
const LiveMarker = isAllActiveView ? AnimatedRiderMarker : Marker;
|
const LiveMarker = isAllActiveView ? AnimatedRiderMarker : Marker;
|
||||||
const positionProps = isAllActiveView
|
const positionProps = isAllActiveView
|
||||||
? { target: [r.lat, r.lon], duration: 15000 }
|
? { target: [r.lat, r.lon], duration: 1000 }
|
||||||
: { position: [r.lat, r.lon] };
|
: { position: [r.lat, r.lon] };
|
||||||
return (
|
return (
|
||||||
<LiveMarker
|
<LiveMarker
|
||||||
|
|||||||
Reference in New Issue
Block a user