diff --git a/src/pages/nearle/orders/createorder1.js b/src/pages/nearle/orders/createorder1.js index ed8583d..0e0e7eb 100644 --- a/src/pages/nearle/orders/createorder1.js +++ b/src/pages/nearle/orders/createorder1.js @@ -26,6 +26,7 @@ import { useMediaQuery } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; +import { calculateDrivingDistance, calculateTotalCharge } from '../../../utils/distance'; import { Empty } from 'antd'; import { FaPhoneAlt, FaBox, FaBoxes, FaTruck, FaArrowRight, FaArrowLeft, FaCheck, FaRoute, FaMoneyBillWave, FaChartLine, FaReceipt, FaPaperPlane } from 'react-icons/fa'; import { GiDoorHandle } from 'react-icons/gi'; @@ -365,130 +366,25 @@ const Createorder1 = () => { }, [startPoint, endPoint]); // google distance matrix logic - /* const calculateDistance = async (pickup, drop) => { - const service = new google.maps.DistanceMatrixService(); - const getDistanceMatrix = (origins, destinations, travelMode, unitSystem) => { - return new Promise((resolve, reject) => { - service.getDistanceMatrix( - { - origins: [new google.maps.LatLng(origins.latitude, origins.longitude)], - destinations: [new google.maps.LatLng(destinations.latitude, destinations.longitude)], - travelMode: travelMode, - unitSystem: unitSystem - }, - (response, status) => { - if (status === 'OK') { - resolve(response); - } else { - reject(new Error(`Error calculating distance: ${status}`)); - } - } - ); - }); - }; - try { - // Use await to wait for the promise to resolve - const response = await getDistanceMatrix(pickup, drop, 'DRIVING', google.maps.UnitSystem.METRIC); - - // Handle the response - const results = response.rows[0].elements; - for (let i = 0; i < results.length; i++) { - const element = results[i]; - - // Extract the numerical value of the distance - - const distance = element.distance.value; - console.log('distance in m ', distance); - const distanceInKm = (distance / 1000).toFixed(2); - console.log('distance in km ', distanceInKm); - const roundedDistance = Math.round(distanceInKm); - console.log('roundedDistance', roundedDistance); - setDistance(roundedDistance); - if (roundedDistance < minKm) { - setTotalCharge(basePrice); - } else { - console.log('minKm', minKm); - console.log('pricePerKm', pricePerKm); - console.log('basePrice', basePrice); - const total = (roundedDistance - minKm) * pricePerKm + basePrice; - console.log('total', total); - setTotalCharge(total); - } - setShowDistance(true); - if (roundedDistance > appLocaRadius) { - setShowDistance(true); - setOpen(true); - } - - // Extract the numerical value of the duration - const durationMatch = element.duration.text.match(/([\d.]+)/); - const duration = durationMatch ? parseInt(durationMatch[0]) : null; - - // Display only the numerical values - console.log(`Distance: ${roundedDistance}, Duration: ${duration}`); - } - } catch (error) { - console.error('Error calculating distance:', error); - } - };*/ - // Haversine + 1.3 - const calculateDistance = async (pickup, drop) => { - // Haversine formula - const haversineDistance = (lat1, lon1, lat2, lon2) => { - const toRad = (value) => (value * Math.PI) / 180; - - const R = 6371; // Earth radius in KM - const dLat = toRad(lat2 - lat1); - const dLon = toRad(lon2 - lon1); - - const a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); - - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return R * c; // distance in KM - }; - - try { - // 1. Get aerial distance - const aerialDistance = haversineDistance(pickup.latitude, pickup.longitude, drop.latitude, drop.longitude); - - // 2. Convert to road approximation (1.3x) - const distanceInKm = (aerialDistance * 1.3).toFixed(2); - - console.log('distance in km ', distanceInKm); - - const roundedDistance = Math.round(distanceInKm); - console.log('roundedDistance', roundedDistance); - - // 🔽 SAME AS YOUR EXISTING LOGIC (UNCHANGED) + const roundedDistance = await calculateDrivingDistance(pickup, drop); + console.log('calculated distance in km:', roundedDistance); setDistance(roundedDistance); - if (roundedDistance < minKm) { - setTotalCharge(basePrice); - } else { - console.log('minKm', minKm); - console.log('pricePerKm', pricePerKm); - console.log('basePrice', basePrice); - - const total = (roundedDistance - minKm) * pricePerKm + basePrice; - - console.log('total', total); - setTotalCharge(total); - } + const total = calculateTotalCharge(roundedDistance, basePrice, pricePerKm, minKm); + console.log('total charge:', total); + setTotalCharge(total); setShowDistance(true); - if (roundedDistance > appLocaRadius) { setShowDistance(true); setOpen(true); } - // ⏱️ Approximate duration (optional, since no API) + // ⏱️ Approximate duration const avgSpeed = 40; // km/h (adjust if needed) const duration = Math.round((roundedDistance / avgSpeed) * 60); // minutes - console.log(`Distance: ${roundedDistance}, Duration: ${duration}`); } catch (error) { console.error('Error calculating distance:', error); diff --git a/src/pages/nearle/orders/multipleOrders.js b/src/pages/nearle/orders/multipleOrders.js index 0c61574..834226f 100644 --- a/src/pages/nearle/orders/multipleOrders.js +++ b/src/pages/nearle/orders/multipleOrders.js @@ -7,6 +7,7 @@ import { useNavigate } from 'react-router'; import { useTheme } from '@mui/material/styles'; import useMediaQuery from '@mui/material/useMediaQuery'; import { enqueueSnackbar } from 'notistack'; +import { calculateDrivingDistance, calculateTotalCharge } from '../../../utils/distance'; import { FormControl, @@ -296,53 +297,17 @@ const MultipleOrders = () => { settotalCash(a4); }, [dropCust]); - // ============================== distance (Google) ============================== + // ============================== distance (OSRM/Haversine) ============================== const calculateDistance = async (customer) => { - if (typeof window === 'undefined' || !window.google?.maps?.DistanceMatrixService) { - throw new Error('Google Maps not loaded'); - } - const service = new window.google.maps.DistanceMatrixService(); - - const getDistanceMatrix = (origins, destinations) => - new Promise((resolve, reject) => { - try { - if (!origins || !destinations) return reject(new Error('Origin or destination data missing.')); - service.getDistanceMatrix( - { - origins: [new window.google.maps.LatLng(origins.latitude, origins.longitude)], - destinations: [new window.google.maps.LatLng(destinations.latitude, destinations.longitude)], - travelMode: 'DRIVING', - unitSystem: window.google.maps.UnitSystem.METRIC - }, - (response, status) => { - if (status === 'OK') resolve(response); - else reject(new Error(`Google API error: ${status}`)); - } - ); - } catch (err) { - reject(new Error(`Unexpected error inside DistanceMatrixService: ${err.message}`)); - } - }); - try { if (!customer || typeof customer !== 'object') throw new Error('Invalid customer data.'); if (!pickCust || typeof pickCust !== 'object') throw new Error('Origin (pickCust) data missing or invalid.'); - const response = await getDistanceMatrix(pickCust, customer); - const distVal = response?.rows?.[0]?.elements?.[0]?.distance?.value; - if (distVal == null) throw new Error('Malformed Distance Matrix response: missing distance value.'); - - const km = distVal / 1000; - const roundedDistance = Math.round(km); - const totalcharge = - roundedDistance < minKm ? basePrice : (roundedDistance - minKm) * pricePerKm + basePrice; + const roundedDistance = await calculateDrivingDistance(pickCust, customer); + const totalcharge = calculateTotalCharge(roundedDistance, basePrice, pricePerKm, minKm); return { roundedDistance, totalcharge }; } catch (error) { - if (error.message.includes('Google API')) { - OpenToast('Invalid coordinates or Google API error.', 'error', 3000); - } else if (error.message.includes('Malformed Distance Matrix')) { - OpenToast('Google Distance Matrix returned invalid data.', 'error', 3000); - } else if (error.message.includes('Origin') || error.message.includes('customer')) { + if (error.message.includes('Origin') || error.message.includes('customer') || error.message.includes('coordinates')) { OpenToast('Missing or invalid input data for distance calculation.', 'warning', 3000); } else { OpenToast('Unexpected error during distance calculation.', 'error', 3000); diff --git a/src/pages/nearle/orders/multipleorders_copy.js b/src/pages/nearle/orders/multipleorders_copy.js index 0bdb7b7..6d67847 100644 --- a/src/pages/nearle/orders/multipleorders_copy.js +++ b/src/pages/nearle/orders/multipleorders_copy.js @@ -16,6 +16,7 @@ var utc = require('dayjs/plugin/utc'); dayjs.extend(utc); import { enqueueSnackbar } from 'notistack'; import { useNavigate } from 'react-router'; +import { calculateDrivingDistance, calculateTotalCharge } from '../../../utils/distance'; import Papa from 'papaparse'; import { @@ -344,47 +345,6 @@ const MultipleOrders = () => { // 🔹 Main distance calculation function const calculateDistance = async (customer) => { - const service = new google.maps.DistanceMatrixService(); - - // Helper: safely get distance matrix - const getDistanceMatrix = (origins, destinations) => { - return new Promise((resolve, reject) => { - 2; - try { - if (!origins || !destinations) { - return reject(new Error('Origin or destination data missing.')); - } - - // if ( - // typeof origins.latitude !== 'number' || - // typeof origins.longitude !== 'number' || - // typeof destinations.latitude !== 'number' || - // typeof destinations.longitude !== 'number' - // ) { - // return reject(new Error('Invalid coordinates format. Expected numeric latitude/longitude.')); - // } - - service.getDistanceMatrix( - { - origins: [new google.maps.LatLng(origins.latitude, origins.longitude)], - destinations: [new google.maps.LatLng(destinations.latitude, destinations.longitude)], - travelMode: 'DRIVING', - unitSystem: google.maps.UnitSystem.METRIC - }, - (response, status) => { - if (status === 'OK') { - resolve(response); - } else { - reject(new Error(`Google API error: ${status}`)); - } - } - ); - } catch (err) { - reject(new Error(`Unexpected error inside DistanceMatrixService: ${err.message}`)); - } - }); - }; - try { // --- Input validation --- if (!customer || typeof customer !== 'object') { @@ -395,40 +355,19 @@ const MultipleOrders = () => { throw new Error('Origin (pickCust) data missing or invalid.'); } - // --- Call Google Maps API --- - const response = await getDistanceMatrix(pickCust, customer); - - // --- Validate response structure --- - if (!response.rows?.[0]?.elements?.[0] || !response.rows[0].elements[0].distance?.value) { - throw new Error('Malformed Distance Matrix response: missing distance value.'); - } - // --- Compute distance --- - const distanceInMeters = response.rows[0].elements[0].distance.value; - const distanceInKilometers = distanceInMeters / 1000; - const roundedDistance = Math.round(distanceInKilometers); + const roundedDistance = await calculateDrivingDistance(pickCust, customer); // --- Calculate total charge --- - let totalcharge; - if (roundedDistance < minKm) { - totalcharge = basePrice; - } else { - totalcharge = (roundedDistance - minKm) * pricePerKm + basePrice; - } + const totalcharge = calculateTotalCharge(roundedDistance, basePrice, pricePerKm, minKm); setTotalCharge(totalcharge); return { roundedDistance, totalcharge }; } catch (error) { // --- Categorized smart error handling --- - if (error.message.includes('Google API')) { - console.error('🚨 Google Maps API Error:', error.message); - OpenToastSmart('Google Maps API Error: ' + error.message, 'error'); - } else if (error.message.includes('Invalid coordinates')) { + if (error.message.includes('Invalid coordinates') || error.message.includes('Invalid coordinate')) { console.error('📍 Invalid coordinate format:', error.message); OpenToastSmart('Invalid coordinate format. Check location data.', 'warning'); - } else if (error.message.includes('Malformed Distance Matrix')) { - console.error('⚠️ Unexpected Google response structure:', error.message); - OpenToastSmart('Google Distance Matrix returned invalid data.', 'error'); } else if (error.message.includes('Origin') || error.message.includes('customer')) { console.error('❌ Missing or invalid input data:', error.message); OpenToastSmart('Missing or invalid input data for distance calculation.', 'warning'); diff --git a/src/pages/nearle/orders/newcreateOrder.js b/src/pages/nearle/orders/newcreateOrder.js index b0143be..e137ad3 100644 --- a/src/pages/nearle/orders/newcreateOrder.js +++ b/src/pages/nearle/orders/newcreateOrder.js @@ -28,6 +28,7 @@ import { import CloseIcon from '@mui/icons-material/Close'; import { Empty } from 'antd'; import { FaPhoneAlt } from 'react-icons/fa'; +import { calculateDrivingDistance, calculateTotalCharge } from '../../../utils/distance'; import { GiDoorHandle } from 'react-icons/gi'; import { FaLandmarkDome } from 'react-icons/fa6'; import ClearIcon from '@mui/icons-material/Clear'; @@ -470,67 +471,19 @@ const Createorder1 = () => { }, [startPoint, endPoint]); const calculateDistance = async (pickup, drop) => { - const service = new google.maps.DistanceMatrixService(); - const getDistanceMatrix = (origins, destinations, travelMode, unitSystem) => { - return new Promise((resolve, reject) => { - service.getDistanceMatrix( - { - origins: [new google.maps.LatLng(origins.latitude, origins.longitude)], - destinations: [new google.maps.LatLng(destinations.latitude, destinations.longitude)], - travelMode: travelMode, - unitSystem: unitSystem - }, - (response, status) => { - if (status === 'OK') { - resolve(response); - } else { - reject(new Error(`Error calculating distance: ${status}`)); - } - } - ); - }); - }; - try { - // Use await to wait for the promise to resolve - const response = await getDistanceMatrix(pickup, drop, 'DRIVING', google.maps.UnitSystem.METRIC); + const roundedDistance = await calculateDrivingDistance(pickup, drop); + console.log('calculated distance in km:', roundedDistance); + setDistance(roundedDistance); - // Handle the response - const results = response.rows[0].elements; - for (let i = 0; i < results.length; i++) { - const element = results[i]; + const total = calculateTotalCharge(roundedDistance, basePrice, pricePerKm, minKm); + console.log('total charge:', total); + setTotalCharge(total); - // Extract the numerical value of the distance - - const distance = element.distance.value; - console.log('distance in m ', distance); - const distanceInKm = (distance / 1000).toFixed(2); - console.log('distance in km ', distanceInKm); - const roundedDistance = Math.round(distanceInKm); - console.log('roundedDistance', roundedDistance); - setDistance(roundedDistance); - if (roundedDistance < minKm) { - setTotalCharge(basePrice); - } else { - console.log('minKm', minKm); - console.log('pricePerKm', pricePerKm); - console.log('basePrice', basePrice); - const total = (roundedDistance - minKm) * pricePerKm + basePrice; - console.log('total', total); - setTotalCharge(total); - } + setShowDistance(true); + if (roundedDistance > appLocaRadius) { setShowDistance(true); - if (roundedDistance > appLocaRadius) { - setShowDistance(true); - setOpen(true); - } - - // Extract the numerical value of the duration - const durationMatch = element.duration.text.match(/([\d.]+)/); - const duration = durationMatch ? parseInt(durationMatch[0]) : null; - - // Display only the numerical values - console.log(`Distance: ${roundedDistance}, Duration: ${duration}`); + setOpen(true); } } catch (error) { console.error('Error calculating distance:', error); diff --git a/src/utils/distance.js b/src/utils/distance.js new file mode 100644 index 0000000..9f35c2a --- /dev/null +++ b/src/utils/distance.js @@ -0,0 +1,65 @@ +/** + * Calculates distance (in km) between origin and destination. + * Uses OSRM as primary driving distance source, and falls back to Haversine with a 1.3 multiplier. + * + * @param {Object} origin - { latitude, longitude } + * @param {Object} destination - { latitude, longitude } + * @returns {Promise} - Round distance in KM + */ +export const calculateDrivingDistance = async (origin, destination) => { + const lat1 = origin?.latitude; + const lon1 = origin?.longitude; + const lat2 = destination?.latitude; + const lon2 = destination?.longitude; + + if (lat1 == null || lon1 == null || lat2 == null || lon2 == null) { + throw new Error("Invalid coordinates"); + } + + // 1. Try OSRM API (either self-hosted or public demo server for fallback) + const osrmBaseUrl = process.env.REACT_APP_OSRM_URL || "https://router.project-osrm.org"; + try { + const url = `${osrmBaseUrl}/route/v1/driving/${lon1},${lat1};${lon2},${lat2}?overview=false`; + const response = await fetch(url); + if (response.ok) { + const data = await response.json(); + if (data.routes && data.routes.length > 0) { + const distanceInMeters = data.routes[0].distance; + const distanceInKm = distanceInMeters / 1000; + return Math.round(distanceInKm); + } + } + } catch (error) { + console.warn("OSRM API failed, falling back to Haversine math:", error); + } + + // 2. Fallback: Haversine Formula with 1.3x multiplier + const toRad = (val) => (val * Math.PI) / 180; + const R = 6371; // Earth radius in km + const dLat = toRad(lat2 - lat1); + const dLon = toRad(lon2 - lon1); + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const aerialDistance = R * c; + + return Math.round(aerialDistance * 1.3); +}; + +/** + * Calculates total charge based on distance and pricing tier + * + * @param {number} distanceKm + * @param {number} basePrice + * @param {number} pricePerKm + * @param {number} minKm + * @returns {number} + */ +export const calculateTotalCharge = (distanceKm, basePrice, pricePerKm, minKm) => { + if (distanceKm < minKm) { + return basePrice; + } + return (distanceKm - minKm) * pricePerKm + basePrice; +}; diff --git a/yarn.lock b/yarn.lock index 47d7a71..4118619 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6572,11 +6572,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2, fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"