updates on the map change and the configurations and removed the google map api key

This commit is contained in:
2026-08-04 12:35:56 +05:30
parent d5bde7d1ac
commit e1584e3a4b
17 changed files with 1030 additions and 1620 deletions

3
.env
View File

@@ -4,13 +4,10 @@ GENERATE_SOURCEMAP = false
## Backend API URL
REACT_APP_API_URL=https://mock-data-api-nextjs.vercel.app/
## Google Map Key
REACT_APP_URL=https://jupiter.nearle.app/live/api/v1
REACT_APP_URL2=https://jupiter.nearle.app/live/api/v2
REACT_APP_URL3=https://jupiter.nearle.app/live/api/v3
REACT_APP_STAFF_TOKEN=
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8
REACT_APP_RIDER_ACCESS_ID=AAAAILMpCEU:APA91bEavuOllBI6sFgYtxXAgNmAVwNA-MnCMHLGlR4_t7UqpLajAkdn3T0CZr_zaLBknLyim9ytFLMZgbeXmKqTad_PKCbqlYjHpaizVrLXtecxqyEy4UktIacK2UvHVUATHL-7VQQk

View File

@@ -2,7 +2,6 @@ REACT_APP_URL=https://jupiter.nearle.app/live/api/v1
REACT_APP_URL2=https://jupiter.nearle.app/live/api/v2
REACT_APP_URL3=https://jupiter.nearle.app/live/api/v3
REACT_APP_STAFF_TOKEN=
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8
REACT_APP_RIDER_ACCESS_ID=AAAAILMpCEU:APA91bEavuOllBI6sFgYtxXAgNmAVwNA-MnCMHLGlR4_t7UqpLajAkdn3T0CZr_zaLBknLyim9ytFLMZgbeXmKqTad_PKCbqlYjHpaizVrLXtecxqyEy4UktIacK2UvHVUATHL-7VQQk

View File

@@ -13,12 +13,10 @@
"@mui/lab": "^5.0.0-alpha.127",
"@mui/material": "^5.12.1",
"@mui/x-date-pickers": "^6.18.2",
"@react-google-maps/api": "^2.20.7",
"@reduxjs/toolkit": "^1.9.5",
"@svgr/webpack": "^7.0.0",
"@tanstack/react-query": "^5.17.9",
"antd": "^5.11.5",
"autosuggest-highlight": "^3.3.4",
"axios": "^1.3.5",
"buffer": "^6.0.3",
"chance": "^1.1.11",
@@ -45,8 +43,6 @@
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-geocode": "^0.2.3",
"react-google-autocomplete": "^2.7.3",
"react-icons": "^4.12.0",
"react-intl": "^6.4.1",
"react-leaflet": "^4.2.1",

View File

@@ -0,0 +1,193 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Autocomplete, TextField, CircularProgress } from '@mui/material';
import { debounce } from '@mui/material/utils';
// Free OSM Nominatim geocoder — replaces Google Places Autocomplete /
// react-geocode across the console. No API key required. Nominatim's usage
// policy caps the public endpoint at ~1 req/sec, so every caller here is
// debounced (500ms) and cancels stale in-flight predictions.
const NOMINATIM_SEARCH_URL = 'https://nominatim.openstreetmap.org/search';
// Builds a Google-Places-shaped `address_components` array (each entry
// `{ long_name, short_name, types: [] }`) from Nominatim's flat `address`
// object, so existing call sites' `place.address_components.forEach(...)`
// type-switch parsing keeps working unchanged.
const buildAddressComponents = (addr = {}) => {
const components = [];
const push = (longName, types) => {
if (longName) components.push({ long_name: longName, short_name: longName, types });
};
push(addr.house_number, ['street_number']);
push(addr.road || addr.pedestrian, ['route']);
push(addr.suburb || addr.neighbourhood || addr.quarter, ['sublocality_level_1', 'sublocality']);
push(addr.city_district || addr.county, ['administrative_area_level_3']);
push(addr.city || addr.town || addr.village, ['locality']);
push(addr.state, ['administrative_area_level_1']);
push(addr.country, ['country']);
push(addr.postcode, ['postal_code']);
return components;
};
// Shapes a Nominatim result close enough to a Google Places `place` object
// (formatted_address, geometry.location.lat()/lng(), address_components)
// that call sites need only mechanical edits, not rewrites.
export const toPlace = (result) => ({
formatted_address: result.display_name,
name: result.display_name?.split(',')[0] || '',
geometry: {
location: {
lat: () => parseFloat(result.lat),
lng: () => parseFloat(result.lon)
}
},
address_components: buildAddressComponents(result.address)
});
const buildParams = (query, bias) => {
const params = new URLSearchParams({ q: query, format: 'json', addressdetails: '1', limit: '5' });
if (bias?.lat && bias?.lng) {
const d = 0.5; // ~55km box around the operator's zone — soft bias, not a hard filter
params.set('viewbox', `${bias.lng - d},${bias.lat + d},${bias.lng + d},${bias.lat - d}`);
}
return params;
};
// Standalone forward-geocode helper — replaces `Geocode.fromAddress(address)`
// for call sites that resolve a plain text address without a predictions
// dropdown (e.g. re-geocoding on submit). Returns a `toPlace`-shaped object,
// or null if nothing matched.
export async function geocodeAddress(address, { bias } = {}) {
if (!address) return null;
try {
const params = buildParams(address, bias);
params.set('limit', '1');
const res = await fetch(`${NOMINATIM_SEARCH_URL}?${params.toString()}`, { headers: { Accept: 'application/json' } });
if (!res.ok) return null;
const results = await res.json();
if (!results?.length) return null;
return toPlace(results[0]);
} catch (err) {
console.error('geocodeAddress error:', err);
return null;
}
}
// Address search-as-you-type — replaces Google Places Autocomplete
// (usePlacesWidget / window.google.maps.places.Autocomplete /
// AutocompleteService) across the console. Renders as an MUI Autocomplete
// with freeSolo so the operator can still submit free text.
//
// Props:
// value / onChange(text) — controlled free-text input value
// onPlaceSelected(place) — fired when a suggestion is chosen; `place`
// is shaped like a Google Places `place`
// bias { lat, lng } — optional soft bias toward the operator's zone
const AddressAutocomplete = ({
id,
label,
placeholder = 'Search address',
value,
onChange,
onPlaceSelected,
bias,
sx,
textFieldSx,
fullWidth = true,
disabled,
inputRef,
TextFieldProps = {}
}) => {
const [inputValue, setInputValue] = useState(value || '');
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (value !== undefined && value !== inputValue) setInputValue(value || '');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
const fetchPredictions = useMemo(
() =>
debounce((query, bias1, callback) => {
if (!query) {
callback([]);
return;
}
const params = buildParams(query, bias1);
fetch(`${NOMINATIM_SEARCH_URL}?${params.toString()}`, { headers: { Accept: 'application/json' } })
.then((res) => (res.ok ? res.json() : []))
.then((results) => callback(results || []))
.catch(() => callback([]));
}, 500),
[]
);
useEffect(() => {
let active = true;
if (!inputValue) {
setOptions([]);
setLoading(false);
return undefined;
}
setLoading(true);
fetchPredictions(inputValue, bias, (results) => {
if (!active) return;
setLoading(false);
setOptions(results);
});
return () => {
active = false;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputValue, bias?.lat, bias?.lng, fetchPredictions]);
return (
<Autocomplete
id={id}
freeSolo
fullWidth={fullWidth}
disabled={disabled}
sx={sx}
options={options}
filterOptions={(x) => x}
getOptionLabel={(option) => (typeof option === 'string' ? option : option.display_name || '')}
isOptionEqualToValue={(option, val) => option.place_id === val?.place_id}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
onChange?.(newInputValue);
}}
onChange={(event, selected) => {
if (selected && typeof selected !== 'string') {
setInputValue(selected.display_name || '');
onPlaceSelected?.(toPlace(selected));
}
}}
renderInput={(params) => (
<TextField
{...params}
{...TextFieldProps}
inputRef={inputRef}
label={label}
placeholder={placeholder}
sx={textFieldSx}
autoComplete="off"
InputProps={{
...params.InputProps,
...TextFieldProps.InputProps,
startAdornment: TextFieldProps.InputProps?.startAdornment ?? params.InputProps.startAdornment,
endAdornment: (
<>
{loading ? <CircularProgress color="inherit" size={16} /> : null}
{TextFieldProps.InputProps?.endAdornment ?? params.InputProps.endAdornment}
</>
)
}}
/>
)}
/>
);
};
export default AddressAutocomplete;

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState, useRef, useMemo, Fragment } from 'react';
import React, { useEffect, useState, useRef, Fragment } from 'react';
import MainCard from 'components/MainCard';
import axios from 'axios';
import { useTheme } from '@mui/material/styles';
@@ -6,10 +6,7 @@ import Loader from 'components/Loader';
import { Empty } from 'antd';
import dayjs from 'dayjs';
import { enqueueSnackbar } from 'notistack';
import Geocode from 'react-geocode';
import LocationOnIcon from '@mui/icons-material/LocationOn';
import parse from 'autosuggest-highlight/parse';
import { debounce } from '@mui/material/utils';
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
import {
Stack,
Table,
@@ -184,7 +181,6 @@ const Clients1 = () => {
const [appId, setAppId] = useState(0);
const [locaName, setLocoName] = useState('');
const [locations] = useState('All');
const [value, setValue] = React.useState(null);
const [value0, setValue0] = useState(0);
const [value1, setValue1] = useState(0);
const [value2, setValue2] = useState(0);
@@ -196,7 +192,6 @@ const Clients1 = () => {
const [appPricing, setAppPricing] = useState([]);
const [selectedPricing, setSelectedPricing] = useState({});
const [isPrice, setIsprice] = useState(true);
const [address, setAddress] = useState('');
const [city, setCity] = useState('');
const [zipcode, setZipcode] = useState('');
const [latlong, setLatlong] = useState({});
@@ -258,144 +253,50 @@ const Clients1 = () => {
autoHideDuration: duration
});
};
// ==============================|| google address ||============================== //
const GOOGLE_MAPS_API_KEY = process.env.REACT_APP_GOOGLE_MAPS_API_KEY;
// ==============================|| address autocomplete ||============================== //
const handleAddressPlaceSelected = (place) => {
const lat = place.geometry.location.lat();
const lng = place.geometry.location.lng();
setLatlong({ lat, lng });
setEditClient((prev) => ({
...prev,
address: place.formatted_address,
latitude: lat.toString(),
longitude: lng.toString()
}));
function loadScript(src, position, id) {
if (!position) {
return;
}
const script = document.createElement('script');
script.setAttribute('async', '');
script.setAttribute('id', id);
script.src = src;
position.appendChild(script);
}
const autocompleteService = { current: null };
// ==============================|| for google address ||============================== //
const [inputValue, setInputValue] = useState('');
const [options, setOptions] = useState([]);
const loaded = useRef(false);
if (typeof window !== 'undefined' && !loaded.current) {
if (!document.querySelector('#google-maps')) {
loadScript(
`https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=places`,
document.querySelector('head'),
'google-maps'
);
}
loaded.current = true;
}
const fetch = useMemo(
() =>
debounce((request, callback) => {
autocompleteService.current.getPlacePredictions(request, callback);
}, 400),
[]
);
useEffect(() => {
let active = true;
if (!autocompleteService.current && window.google) {
autocompleteService.current = new window.google.maps.places.AutocompleteService();
}
if (!autocompleteService.current) {
return undefined;
}
if (inputValue === '') {
setOptions(value ? [value] : []);
return undefined;
}
fetch({ input: inputValue }, (results) => {
if (active) {
let newOptions = [];
if (value) {
newOptions = [value];
let city1, zipcode1, state1, suburb1;
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'locality':
city1 = component.long_name;
break;
case 'administrative_area_level_1':
state1 = component.long_name;
break;
case 'postal_code':
zipcode1 = component.long_name;
break;
case 'sublocality':
case 'sublocality_level_1':
suburb1 = component.long_name;
break;
}
if (results) {
newOptions = [...newOptions, ...results];
}
setOptions(newOptions);
}
});
});
return () => {
active = false;
};
}, [value, inputValue, fetch]);
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
useEffect(() => {
try {
console.log('selected address =>', address);
Geocode.fromAddress(address).then(
(response) => {
console.log('lat long response =>', response.results[0]);
if (response.status == 'OK') {
const { lat, lng } = response.results[0].geometry.location;
setLatlong({
lat,
lng
});
setEditClient({
...editClient,
latitude: lat.toString(),
longitude: lng.toString()
});
// setSelectedCustomer({
// ...selectedCustomer,
// latitude: lat,
// longitude: lng,
// });
if (response.results[0].address_components) {
let place = response.results[0];
let city1, zipcode1, state1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state1 = place.address_components[i].long_name;
break;
case 'postal_code':
zipcode1 = place.address_components[i].long_name;
break;
case 'sublocality':
suburb1 = place.address_components[i].long_name;
break;
}
}
}
setCity(city1 || '');
setState(state1 || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
setEditClient({
...editClient,
city: city1 || '',
state: state1 || '',
postcode: zipcode || '',
suburb: suburb1 || ''
});
}
}
},
(error) => {
console.log(error);
}
);
} catch (err) {
console.log(err);
}
}, [address]);
setCity(city1 || '');
setState(state1 || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
setEditClient((prev) => ({
...prev,
city: city1 || '',
state: state1 || '',
postcode: zipcode1 || '',
suburb: suburb1 || ''
}));
};
useEffect(() => {
selectedCustomer &&
@@ -1562,85 +1463,19 @@ const Clients1 = () => {
/>
</Grid>
<Grid item xs zeroMinWidth>
<Autocomplete
id="google-map-demo"
sx={{}}
<AddressAutocomplete
id="address-autocomplete"
fullWidth
label={'Address'}
getOptionLabel={(option) => (typeof option === 'string' ? option : option.description)}
filterOptions={(x) => x}
options={options}
autoComplete
includeInputInList
filterSelectedOptions
defaultValue={selectedCustomer.address}
noOptionsText="No locations"
onChange={(event, newValue) => {
setOptions(newValue ? [newValue, ...options] : options);
setValue(newValue || '');
console.log('newValue', newValue);
setAddress(newValue?.description || '');
setEditClient({
...editClient,
address: newValue?.description || ''
});
}}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
renderInput={(params) => (
<TextField {...params} fullWidth label="Address" variant="outlined" />
)}
renderOption={(props, option) => {
const matches = option.structured_formatting.main_text_matched_substrings || [];
const parts = parse(
option.structured_formatting.main_text,
matches.map((match) => [match.offset, match.offset + match.length])
);
return (
<li {...props}>
<Grid container alignItems="center">
<Grid
item
sx={{
display: 'flex',
width: 44
}}
>
<LocationOnIcon
sx={{
color: 'text.secondary'
}}
/>
</Grid>
<Grid
item
sx={{
width: 'calc(100% - 44px)',
wordWrap: 'break-word'
}}
>
{parts.map((part, index) => (
<Box
key={index}
component="span"
sx={{
fontWeight: part.highlight ? 'bold' : 'regular'
}}
>
{part.text}
</Box>
))}
<Typography variant="body2" color="text.secondary">
{option.structured_formatting.secondary_text}
</Typography>
</Grid>
</Grid>
</li>
);
}}
label="Address"
value={editClient?.address ?? selectedCustomer.address ?? ''}
onChange={(text) =>
setEditClient((prev) => ({
...prev,
address: text
}))
}
onPlaceSelected={handleAddressPlaceSelected}
TextFieldProps={{ variant: 'outlined' }}
/>
</Grid>
</Grid>

View File

@@ -4,7 +4,7 @@ import { Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typograph
import MainCard from 'components/MainCard';
import axios from 'axios';
import Loader from 'components/Loader';
import Geocode from 'react-geocode';
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
import { enqueueSnackbar } from 'notistack';
import { useNavigate } from 'react-router';
import CloseIcon from '@mui/icons-material/Close';
@@ -25,7 +25,6 @@ const CreateCustomer = () => {
const [inputValue2, setInputValue2] = useState('');
const [appLocaLat, setAppLocaLat] = useState();
const [appLocaLng, setAppLocaLng] = useState();
const [appLocaRadius, setAppLocaRadius] = useState();
const [locaName, setLocoName] = useState('Select Location');
const [tenantlist, setTenantlist] = useState([]);
const [tid, setTid] = useState(0);
@@ -34,92 +33,68 @@ const CreateCustomer = () => {
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
useEffect(() => {
// Initialize Google Maps Autocomplete
if (inputValue2) {
const autocompleteInput = document.getElementById('addressAuto1');
const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, {
// types: ['(cities)'], // You can adjust the types parameter based on your requirements
strictBounds: true,
bounds: new window.google.maps.Circle({
// center: new window.google.maps.LatLng(11.0050707, 76.9509083),
// radius: 100000
center: new window.google.maps.LatLng(appLocaLat, appLocaLng),
radius: appLocaRadius * 1000
}).getBounds()
const handlePlaceSelected = (place) => {
setInputValue2(`${place.name}, ${place.formatted_address}`);
// to trigger getDistance
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setAddress(`${place.name} ${place.formatted_address}`);
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
// Event listener for autocomplete place changed
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
setInputValue2(`${place.name}, ${place.formatted_address}`);
console.log('new place', place); // Do something with the selected place
console.log(' pick (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place
// to trigger getDistance
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setAddress(`${place.name} ${place.formatted_address}`);
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
});
});
// Use address object as per your requirements
setPickCust({
...pickCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.administrative_area_level_3,
city: address.locality,
state: address.administrative_area_level_1,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
console.log('Pick Address:', address);
});
}
}, [inputValue2]);
// Use address object as per your requirements
setPickCust({
...pickCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.administrative_area_level_3,
city: address.locality,
state: address.administrative_area_level_1,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
};
// ==================================================== || getapplocations || ====================================================
const getapplocations = async () => {
setLoading(true);
@@ -127,12 +102,10 @@ const CreateCustomer = () => {
.get(`${process.env.REACT_APP_URL}/utils/getapplocations/?applocationid=${appId}`)
.then((res) => {
console.log('getapplocations', res);
const { latitude, longitude, radius } = res.data.details[0];
const { latitude, longitude } = res.data.details[0];
if (res.data.status) {
setAppLocaLat(latitude);
setAppLocaLng(longitude);
setAppLocaRadius(radius);
console.log('radius', radius);
}
setLoading(false);
})
@@ -380,38 +353,42 @@ const CreateCustomer = () => {
<Grid item xs={12}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-email"> Address</InputLabel>
<TextField
variant="outlined"
<AddressAutocomplete
id="addressAuto1"
fullWidth
value={inputValue2}
onChange={(e) => {
onChange={(text) => {
if (appId) {
appId && setInputValue2(e.target.value);
setInputValue2(text);
} else {
OpenToast('Select Location First', 'warning', 3000);
}
}}
InputProps={{
endAdornment: (
<IconButton
onClick={() => {
setInputValue2('');
setPickCust({
...pickCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setStartPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon />
</IconButton>
)
onPlaceSelected={handlePlaceSelected}
bias={{ lat: appLocaLat, lng: appLocaLng }}
TextFieldProps={{
variant: 'outlined',
InputProps: {
endAdornment: (
<IconButton
onClick={() => {
setInputValue2('');
setPickCust({
...pickCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setStartPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon />
</IconButton>
)
}
}}
/>
</Stack>

View File

@@ -10,12 +10,10 @@ import { Box, Button, FormLabel, Grid, InputLabel, MenuItem, Select, Stack, Text
// project import
import MainCard from 'components/MainCard';
import axios from 'axios';
import { usePlacesWidget } from 'react-google-autocomplete';
import AddressAutocomplete, { geocodeAddress } from 'components/nearle_components/AddressAutocomplete';
import Loader from 'components/Loader';
import Geocode from 'react-geocode';
import { enqueueSnackbar } from 'notistack';
import { useNavigate } from 'react-router';
// import { setLocationType } from 'react-geocode';
// const avatarImage = require.context('assets/images/users', true);
@@ -49,9 +47,6 @@ const Createclient = () => {
const navigate = useNavigate();
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
// Geocode.setApiKey('AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8');
const [loading, setLoading] = useState(false);
useEffect(() => {
@@ -63,25 +58,15 @@ const Createclient = () => {
}, []);
useEffect(() => {
try {
Geocode.fromAddress(address).then(
(response) => {
if (response.status == 'OK') {
const { lat, lng } = response.results[0].geometry.location;
setLatlong({
lat,
lng
});
console.log(response);
}
},
(error) => {
console.log(error);
}
);
} catch (err) {
console.log(err);
}
let active = true;
geocodeAddress(address).then((place) => {
if (active && place) {
setLatlong({ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() });
}
});
return () => {
active = false;
};
}, [address]);
const opentoast = (message) => {
@@ -151,45 +136,33 @@ const Createclient = () => {
}
}, [selectedImage]);
const { ref: materialRef } = usePlacesWidget({
apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
onPlaceSelected: (place) => {
console.log(place);
setAddress(place.formatted_address);
let city1, zipcode1, state1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state1 = place.address_components[i].long_name;
break;
case 'postal_code':
zipcode1 = place.address_components[i].long_name;
break;
case 'sublocality':
suburb1 = place.address_components[i].long_name;
break;
}
const handleAddressPlaceSelected = (place) => {
setAddress(place.formatted_address);
let city1, zipcode1, state1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state1 = place.address_components[i].long_name;
break;
case 'postal_code':
zipcode1 = place.address_components[i].long_name;
break;
case 'sublocality':
case 'sublocality_level_1':
suburb1 = place.address_components[i].long_name;
break;
}
}
setCity(city1 || '');
setState(state1 || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
// setAddress(place.formatted_address)
},
// inputAutocompleteValue: "country",
options: {
// componentRestrictions: 'us',
// types: ["establishment"]
types: ['address' || 'geocode']
}
});
setCity(city1 || '');
setState(state1 || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
};
const createprofile = async () => {
console.log('res', businessname, businessno, mobilenumber, emailaddress, address, city, zipcode);
@@ -486,14 +459,13 @@ const Createclient = () => {
<Grid item xs={12}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-address">Address</InputLabel>
<TextField
fullWidth
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
<AddressAutocomplete
id="personal-address"
fullWidth
placeholder="Address"
value={address}
onChange={(e) => setAddress(e.target.value)}
inputRef={materialRef}
onChange={setAddress}
onPlaceSelected={handleAddressPlaceSelected}
/>
</Stack>
</Grid>

View File

@@ -1,4 +1,4 @@
import { React, useState, useEffect, useRef, useMemo } from 'react';
import { React, useState, useEffect, useRef } from 'react';
import axios from 'axios';
import { FaRegEdit } from 'react-icons/fa';
import LoaderWithImage from 'components/nearle_components/LoaderWithImage';
@@ -23,7 +23,6 @@ import {
DialogContent,
Button,
TextField,
Autocomplete,
Avatar,
Paper,
useMediaQuery,
@@ -42,12 +41,9 @@ import {
MdOutlineHowToReg,
MdOutlinePlace
} from 'react-icons/md';
import Geocode from 'react-geocode';
import LocationOnIcon from '@mui/icons-material/LocationOn';
import parse from 'autosuggest-highlight/parse';
import { debounce } from '@mui/material/utils';
// project imports
import Loader from 'components/Loader';
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
import PageHeader from 'components/nearle_components/PageHeader';
@@ -111,23 +107,6 @@ const AccentAvatar = ({ color, selected, size = 24, children }) => (
</Avatar>
);
// ==============================|| google address ||============================== //
const GOOGLE_MAPS_API_KEY = process.env.REACT_APP_GOOGLE_MAPS_API_KEY;
function loadScript(src, position, id) {
if (!position) {
return;
}
const script = document.createElement('script');
script.setAttribute('async', '');
script.setAttribute('id', id);
script.src = src;
position.appendChild(script);
}
const autocompleteService = { current: null };
// ==============================|| MUI TABLE - ENHANCED ||============================== //
export default function Customers() {
@@ -141,7 +120,6 @@ export default function Customers() {
const [locaName, setLocoName] = useState('All');
const [selectedCustomer, setSelectedCustomer] = useState({}); // to edit
const [open, setOpen] = useState(false);
const [address, setAddress] = useState('');
const [latlong, setLatlong] = useState({});
const [city, setCity] = useState('');
const [postcode, setPostcode] = useState('');
@@ -150,136 +128,47 @@ export default function Customers() {
const [searchword, setSearchword] = useState('');
const [debouncedSearch, setDebouncedSearch] = useState('');
// ==============================|| for google address ||============================== //
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
const [options, setOptions] = useState([]);
const loaded = useRef(false);
if (typeof window !== 'undefined' && !loaded.current) {
if (!document.querySelector('#google-maps')) {
loadScript(
`https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAPS_API_KEY}&libraries=places`,
document.querySelector('head'),
'google-maps'
);
}
// ==============================|| address autocomplete ||============================== //
const handleAddressPlaceSelected = (place) => {
const lat = place.geometry.location.lat();
const lng = place.geometry.location.lng();
setLatlong({ lat, lng });
loaded.current = true;
}
const fetch = useMemo(
() =>
debounce((request, callback) => {
autocompleteService.current.getPlacePredictions(request, callback);
}, 400),
[]
);
useEffect(() => {
let active = true;
if (!autocompleteService.current && window.google) {
autocompleteService.current = new window.google.maps.places.AutocompleteService();
}
if (!autocompleteService.current) {
return undefined;
}
if (inputValue === '') {
setOptions(value ? [value] : []);
return undefined;
}
fetch({ input: inputValue }, (results) => {
if (active) {
let newOptions = [];
if (value) {
newOptions = [value];
let city1, zipcode1, state1, suburb1;
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'locality':
city1 = component.long_name;
break;
case 'administrative_area_level_1':
state1 = component.long_name;
break;
case 'postal_code':
zipcode1 = component.long_name;
break;
case 'sublocality':
case 'sublocality_level_1':
suburb1 = component.long_name;
break;
}
if (results) {
newOptions = [...newOptions, ...results];
}
setOptions(newOptions);
}
});
});
return () => {
active = false;
};
}, [value, inputValue, fetch]);
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
useEffect(() => {
try {
console.log('selected address =>', address);
Geocode.fromAddress(address).then(
(response) => {
console.log('lat long response =>', response.results[0]);
if (response.status == 'OK') {
const { lat, lng } = response.results[0].geometry.location;
setLatlong({
lat,
lng
});
// setSelectedCustomer({
// ...selectedCustomer,
// latitude: lat,
// longitude: lng
// });
if (response.results[0].address_components) {
let place = response.results[0];
let city1, zipcode1, state1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state1 = place.address_components[i].long_name;
break;
case 'postal_code':
zipcode1 = place.address_components[i].long_name;
break;
case 'sublocality':
suburb1 = place.address_components[i].long_name;
break;
}
}
}
setCity(city1 || '');
setState(state1 || '');
setPostcode(zipcode1 || '');
setSuburb(suburb1 || '');
setSelectedCustomer((prev) => ({
...prev,
city: city1 || '',
state: state1 || '',
postcode: zipcode1 || '',
suburb: suburb1 || '',
latitude: lat || '',
longitude: lng || ''
}));
}
}
},
(error) => {
console.log(error);
}
);
} catch (err) {
console.log(err);
}
}, [address]);
// useEffect(() => {
// selectedCustomer &&
// setLatlong({
// lat: selectedCustomer.latitude,
// lng: selectedCustomer.longitude
// });
// }, [selectedCustomer]);
setCity(city1 || '');
setState(state1 || '');
setPostcode(zipcode1 || '');
setSuburb(suburb1 || '');
setSelectedCustomer((prev) => ({
...prev,
address: place.formatted_address,
city: city1 || '',
state: state1 || '',
postcode: zipcode1 || '',
suburb: suburb1 || '',
latitude: lat || '',
longitude: lng || ''
}));
};
// ==============================|| getallcustomers (customers) ||============================== //
@@ -891,72 +780,17 @@ export default function Customers() {
</Grid>
<Grid item xs={12}>
<Typography sx={{ mb: 1 }}>Address</Typography>
<Autocomplete
id="google-map-demo"
sx={{}}
<AddressAutocomplete
id="address-autocomplete"
fullWidth
getOptionLabel={(option) => (typeof option === 'string' ? option : option?.description || '')}
filterOptions={(x) => x}
options={options}
autoComplete
includeInputInList
filterSelectedOptions
value={selectedCustomer?.address}
noOptionsText="No locations"
onChange={(event, newValue) => {
setOptions(newValue ? [newValue, ...options] : options);
setValue(newValue);
console.log('newValue', newValue || '');
setAddress(newValue?.description);
setSelectedCustomer({
...selectedCustomer,
address: newValue?.description
});
}}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
renderInput={(params) => <TextField {...params} fullWidth />}
renderOption={(props, option) => {
const matches = option.structured_formatting.main_text_matched_substrings || [];
const parts = parse(
option.structured_formatting.main_text,
matches.map((match) => [match.offset, match.offset + match.length])
);
return (
<li {...props}>
<Grid container alignItems="center">
<Grid item sx={{ display: 'flex', width: 44 }}>
<LocationOnIcon sx={{ color: 'text.secondary' }} />
</Grid>
<Grid
item
sx={{
width: 'calc(100% - 44px)',
wordWrap: 'break-word'
}}
>
{parts?.map((part, index) => (
<Box
key={index}
component="span"
sx={{
fontWeight: part.highlight ? 'bold' : 'regular'
}}
>
{part.text}
</Box>
))}
<Typography variant="body2" color="text.secondary">
{option?.structured_formatting.secondary_text}
</Typography>
</Grid>
</Grid>
</li>
);
}}
value={selectedCustomer?.address || ''}
onChange={(text) =>
setSelectedCustomer((prev) => ({
...prev,
address: text
}))
}
onPlaceSelected={handleAddressPlaceSelected}
/>
</Grid>
<Grid item xs={6}>

View File

@@ -37,7 +37,7 @@ import { TbMapPinCode } from 'react-icons/tb';
import { FaLocationDot } from 'react-icons/fa6';
import axios from 'axios';
import { useTheme } from '@mui/material/styles';
import Geocode from 'react-geocode';
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
import Loader from 'components/Loader';
import * as geolib from 'geolib';
import MainCard from 'components/MainCard';
@@ -169,22 +169,7 @@ const OrderMap = ({ startPoint, endPoint, appLocaLat, appLocaLng }) => {
);
};
function loadScript(src, position, id) {
if (!position) {
return;
}
const script = document.createElement('script');
script.setAttribute('async', '');
script.setAttribute('id', id);
script.src = src;
position.appendChild(script);
}
const Createorder1 = () => {
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
// ================================================= || GoogleMaps (Drawer) || =================================================
const loaded = React.useRef(false);
const navigate = useNavigate();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
@@ -275,17 +260,6 @@ const Createorder1 = () => {
console.log('pickupSlot', pickupSlot);
}, [pickupSlotsList, pickupSlot]);
if (typeof window !== 'undefined' && !loaded.current) {
if (!document.querySelector('#google-maps')) {
loadScript(
`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&libraries=places&location=10.3656,77.9690&radius=50000&components=country:IN&strictbounds=true`,
document.querySelector('head'),
'google-maps'
);
}
loaded.current = true;
}
// ==============================|| fetchAppLocations ||============================== //
const fetchAppLocations = async () => {
try {
@@ -760,173 +734,129 @@ const Createorder1 = () => {
setLoading(false);
});
};
// ============================================= || Google Maps Autocomplete(pick) || =============================================
useEffect(() => {
// Initialize Google Maps Autocomplete
if (inputValue1) {
const autocompleteInput = document.getElementById('addressAuto1');
const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, {
// types: ['(cities)'], // You can adjust the types parameter based on your requirements
strictBounds: true,
bounds: new window.google.maps.Circle({
// center: new window.google.maps.LatLng(11.0050707, 76.9509083),
// radius: 100000
center: new window.google.maps.LatLng(appLocaLat, appLocaLng),
radius: appLocaRadius * 1000
}).getBounds()
// ============================================= || Address Autocomplete (pick) || =============================================
const handlePickPlaceSelected = (place) => {
setInputValue1(`${place.name}, ${place.formatted_address}`);
// to trigger getDistance
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
});
// Event listener for autocomplete place changed
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
setInputValue1(`${place.name}, ${place.formatted_address}`);
console.log('new place', place); // Do something with the selected place
console.log(' pick (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place
// to trigger getDistance
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
});
// Use address object as per your requirements
setPickCust({
...pickCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.sublocality_level_1,
city: address.locality,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
};
// ============================================= || Address Autocomplete (Drop) || =============================================
// Use address object as per your requirements
setPickCust({
...pickCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.sublocality_level_1,
city: address.locality,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
console.log('Pick Address:', address);
const handleDropPlaceSelected = (place) => {
setInputValue2(`${place.name}, ${place.formatted_address}`);
setEndPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setDropCust({ ...dropCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
}
}, [inputValue1]);
// ============================================= || Google Maps Autocomplete(Drop) || =============================================
});
useEffect(() => {
if (inputValue2) {
// Initialize Google Maps Autocomplete
const autocompleteInput = document.getElementById('addressAuto2');
const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, {
// types: ['(cities)'], // You can adjust the types parameter based on your requirements
strictBounds: true,
bounds: new window.google.maps.Circle({
// center: new window.google.maps.LatLng(11.0050707, 76.9509083),
center: new window.google.maps.LatLng(appLocaLat, appLocaLng),
radius: appLocaRadius * 1000 //km to m
// radius: 100000 //km to m
}).getBounds()
});
let arr = [];
// Event listener for autocomplete place changed
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
setInputValue2(`${place.name}, ${place.formatted_address}`);
console.log('new place', place); // Do something with the selected place
console.log('drop (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place
setEndPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setDropCust({ ...dropCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
});
// Use address object as per your requirements
setDropCust({
...dropCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.sublocality_level_1,
city: address.locality,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
console.log('Drop Address:', address);
});
}
}, [inputValue2]);
// Use address object as per your requirements
setDropCust({
...dropCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.sublocality_level_1,
city: address.locality,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
};
// ============================================= || gettenantlocations (branches) || =============================================
const gettenantlocations = async (id) => {
@@ -1375,43 +1305,47 @@ const Createorder1 = () => {
Address Lookup
</Typography>
{addId1 == 0 ? (
<TextField
<AddressAutocomplete
id="addressAuto1"
fullWidth
size="small"
placeholder="Search Google Maps for an address..."
variant="outlined"
placeholder="Search for an address..."
value={inputValue1 || ''}
onChange={(e) => setInputValue1(e.target.value)}
helperText="Start typing to auto-fill the address details below"
FormHelperTextProps={{ className: 'address-search-helper pickup-helper' }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchOutlined style={{ fontSize: 15, color: '#662582' }} />
</InputAdornment>
),
endAdornment: (
<IconButton
onClick={() => {
setInputValue1('');
setPickCust({
...pickCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setShowDistance(false);
setStartPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon fontSize="small" />
</IconButton>
),
style: { borderRadius: '10px', background: '#fff' }
onChange={setInputValue1}
onPlaceSelected={handlePickPlaceSelected}
bias={{ lat: appLocaLat, lng: appLocaLng }}
TextFieldProps={{
size: 'small',
variant: 'outlined',
helperText: 'Start typing to auto-fill the address details below',
FormHelperTextProps: { className: 'address-search-helper pickup-helper' },
InputProps: {
startAdornment: (
<InputAdornment position="start">
<SearchOutlined style={{ fontSize: 15, color: '#662582' }} />
</InputAdornment>
),
endAdornment: (
<IconButton
onClick={() => {
setInputValue1('');
setPickCust({
...pickCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setShowDistance(false);
setStartPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon fontSize="small" />
</IconButton>
),
style: { borderRadius: '10px', background: '#fff' }
}
}}
/>
) : (
@@ -1693,43 +1627,47 @@ const Createorder1 = () => {
Address Lookup
</Typography>
{addId2 == 0 ? (
<TextField
<AddressAutocomplete
id="addressAuto2"
placeholder="Search Google Maps for an address..."
variant="outlined"
size="small"
placeholder="Search for an address..."
fullWidth
value={inputValue2 || ''}
onChange={(e) => setInputValue2(e.target.value)}
helperText="Start typing to auto-fill the address details below"
FormHelperTextProps={{ className: 'address-search-helper drop-helper' }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchOutlined style={{ fontSize: 15, color: '#65387a' }} />
</InputAdornment>
),
endAdornment: (
<IconButton
onClick={() => {
setInputValue2('');
setDropCust({
...dropCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setShowDistance(false);
setEndPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon fontSize="small" />
</IconButton>
),
style: { borderRadius: '10px', background: '#fff' }
onChange={setInputValue2}
onPlaceSelected={handleDropPlaceSelected}
bias={{ lat: appLocaLat, lng: appLocaLng }}
TextFieldProps={{
size: 'small',
variant: 'outlined',
helperText: 'Start typing to auto-fill the address details below',
FormHelperTextProps: { className: 'address-search-helper drop-helper' },
InputProps: {
startAdornment: (
<InputAdornment position="start">
<SearchOutlined style={{ fontSize: 15, color: '#65387a' }} />
</InputAdornment>
),
endAdornment: (
<IconButton
onClick={() => {
setInputValue2('');
setDropCust({
...dropCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setShowDistance(false);
setEndPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon fontSize="small" />
</IconButton>
),
style: { borderRadius: '10px', background: '#fff' }
}
}}
/>
) : (

View File

@@ -37,7 +37,7 @@ import { TbMapPinCode } from 'react-icons/tb';
import { FaLocationDot } from 'react-icons/fa6';
import axios from 'axios';
import { useTheme } from '@mui/material/styles';
import Geocode from 'react-geocode';
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
import Loader from 'components/Loader';
import MainCard from 'components/MainCard';
import { FaUser } from 'react-icons/fa6';
@@ -280,22 +280,7 @@ const OrderMap = ({ startPoint, endPoint, appLocaLat, appLocaLng }) => {
};
function loadScript(src, position, id) {
if (!position) {
return;
}
const script = document.createElement('script');
script.setAttribute('async', '');
script.setAttribute('id', id);
script.src = src;
position.appendChild(script);
}
const Createorder1 = () => {
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
// ================================================= || GoogleMaps (Drawer) || =================================================
const loaded = React.useRef(false);
const navigate = useNavigate();
const theme = useTheme();
const locationRef = useRef(null);
@@ -380,17 +365,6 @@ const Createorder1 = () => {
}
};
if (typeof window !== 'undefined' && !loaded.current) {
if (!document.querySelector('#google-maps')) {
loadScript(
`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&libraries=places&location=10.3656,77.9690&radius=50000&components=country:IN&strictbounds=true`,
document.querySelector('head'),
'google-maps'
);
}
loaded.current = true;
}
// ==============================|| fetchAppLocations ||============================== //
const fetchAppLocations = async () => {
try {
@@ -863,173 +837,129 @@ const Createorder1 = () => {
setLoading(false);
});
};
// ============================================= || Google Maps Autocomplete(pick) || =============================================
useEffect(() => {
// Initialize Google Maps Autocomplete
if (inputValue1) {
const autocompleteInput = document.getElementById('addressAuto1');
const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, {
// types: ['(cities)'], // You can adjust the types parameter based on your requirements
strictBounds: true,
bounds: new window.google.maps.Circle({
// center: new window.google.maps.LatLng(11.0050707, 76.9509083),
// radius: 100000
center: new window.google.maps.LatLng(appLocaLat, appLocaLng),
radius: appLocaRadius * 1000
}).getBounds()
// ============================================= || Address Autocomplete (pick) || =============================================
const handlePickPlaceSelected = (place) => {
setInputValue1(`${place.name}, ${place.formatted_address}`);
// to trigger getDistance
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
});
// Event listener for autocomplete place changed
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
setInputValue1(`${place.name}, ${place.formatted_address}`);
console.log('new place', place); // Do something with the selected place
console.log(' pick (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place
// to trigger getDistance
setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
});
// Use address object as per your requirements
setPickCust({
...pickCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.sublocality_level_1,
city: address.locality,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
};
// ============================================= || Address Autocomplete (Drop) || =============================================
// Use address object as per your requirements
setPickCust({
...pickCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.sublocality_level_1,
city: address.locality,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
console.log('Pick Address:', address);
const handleDropPlaceSelected = (place) => {
setInputValue2(`${place.name}, ${place.formatted_address}`);
setEndPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setDropCust({ ...dropCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
}
}, [inputValue1]);
// ============================================= || Google Maps Autocomplete(Drop) || =============================================
});
useEffect(() => {
if (inputValue2) {
// Initialize Google Maps Autocomplete
const autocompleteInput = document.getElementById('addressAuto2');
const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, {
// types: ['(cities)'], // You can adjust the types parameter based on your requirements
strictBounds: true,
bounds: new window.google.maps.Circle({
// center: new window.google.maps.LatLng(11.0050707, 76.9509083),
center: new window.google.maps.LatLng(appLocaLat, appLocaLng),
radius: appLocaRadius * 1000 //km to m
// radius: 100000 //km to m
}).getBounds()
});
let arr = [];
// Event listener for autocomplete place changed
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
setInputValue2(`${place.name}, ${place.formatted_address}`);
console.log('new place', place); // Do something with the selected place
console.log('drop (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place
setEndPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
setDropCust({ ...dropCust, address: `${place.name} ${place.formatted_address}` });
const address = {
address: `${place.name} ${place.formatted_address}`,
street_number: '',
route: '',
locality: '',
sublocality_level_1: '',
administrative_area_level_3: '',
administrative_area_level_1: '',
country: '',
postal_code: ''
};
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'street_number':
address.street_number = component.long_name;
break;
case 'route':
address.route = component.long_name;
break;
case 'locality':
address.locality = component.long_name;
break;
case 'sublocality_level_1':
address.sublocality_level_1 = component.long_name;
break;
case 'administrative_area_level_3':
address.administrative_area_level_3 = component.long_name;
break;
case 'administrative_area_level_1':
address.administrative_area_level_1 = component.long_name;
break;
case 'country':
address.country = component.long_name;
break;
case 'postal_code':
address.postal_code = component.long_name;
break;
// Add more cases as needed for other types
}
});
});
// Use address object as per your requirements
setDropCust({
...dropCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.sublocality_level_1,
city: address.locality,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
console.log('Drop Address:', address);
});
}
}, [inputValue2]);
// Use address object as per your requirements
setDropCust({
...dropCust,
address: address.address,
doorno: `${address.street_number} ${address.route}`,
suburb: address.sublocality_level_1,
city: address.locality,
postcode: address.postal_code,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng()
});
};
// ============================================= || gettenantlocations (branches) || =============================================
const gettenantlocations = async (id) => {
@@ -1422,35 +1352,38 @@ const Createorder1 = () => {
<Stack spacing={1.25} sx={{ mt: 0 }}>
{addId1 == 0 ? (
<div>
<TextField
// disabled={!appId || !tenantid || !locationid}
<AddressAutocomplete
id="addressAuto1"
fullWidth
label={'Address'}
variant="outlined"
value={inputValue1 || ''}
onChange={(e) => setInputValue1(e.target.value)}
InputProps={{
endAdornment: (
<IconButton
onClick={() => {
setInputValue1('');
setPickCust({
...pickCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setShowDistance(false);
setStartPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon />
</IconButton>
)
onChange={setInputValue1}
onPlaceSelected={handlePickPlaceSelected}
bias={{ lat: appLocaLat, lng: appLocaLng }}
TextFieldProps={{
variant: 'outlined',
InputProps: {
endAdornment: (
<IconButton
onClick={() => {
setInputValue1('');
setPickCust({
...pickCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setShowDistance(false);
setStartPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon />
</IconButton>
)
}
}}
/>
</div>
@@ -1799,35 +1732,38 @@ const Createorder1 = () => {
<Stack spacing={1.25} sx={{ mt: 0 }}>
{addId2 == 0 ? (
<div>
<TextField
// disabled={!appId || !tenantid || !locationid}
<AddressAutocomplete
id="addressAuto2"
label="Address "
variant="outlined"
fullWidth
value={inputValue2 || ''}
onChange={(e) => setInputValue2(e.target.value)}
InputProps={{
endAdornment: (
<IconButton
onClick={() => {
setInputValue2('');
setDropCust({
...dropCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setShowDistance(false);
setEndPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon />
</IconButton>
)
onChange={setInputValue2}
onPlaceSelected={handleDropPlaceSelected}
bias={{ lat: appLocaLat, lng: appLocaLng }}
TextFieldProps={{
variant: 'outlined',
InputProps: {
endAdornment: (
<IconButton
onClick={() => {
setInputValue2('');
setDropCust({
...dropCust,
doorno: '',
suburb: '',
city: '',
postcode: '',
landmark: ''
});
setShowDistance(false);
setEndPoint({ latitude: 0, longitude: 0 });
}}
size="small"
>
<CloseIcon />
</IconButton>
)
}
}}
/>
</div>

View File

@@ -1,91 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { LoadScriptNext, GoogleMap } from '@react-google-maps/api';
const containerStyle = {
width: '100%',
height: '90vh'
};
const MapWithRouteGoogle = ({ coordinates, additionalProps, setMapOpen }) => {
const mapRef = useRef(null);
/** Convert coordinates to numbers */
const numericCoordinates = coordinates
.map((c) => {
const lat = Number(c.lat);
const lng = Number(c.lng);
return isNaN(lat) || isNaN(lng) ? null : { lat, lng };
})
.filter(Boolean);
if (numericCoordinates.length < 2) {
return <div>No route data available</div>;
}
const start = numericCoordinates[0];
const end = numericCoordinates[numericCoordinates.length - 1];
/** Map loaded callback */
const onMapLoad = (map) => {
// draw markers
new window.google.maps.Marker({
position: start,
map,
label: 'S',
title: `Start: ${additionalProps?.riderStart}`
});
new window.google.maps.Marker({
position: end,
map,
label: 'E',
title: `End: ${additionalProps?.riderEnd}`
});
// draw rider route (point-to-point)
const route = new window.google.maps.Polyline({
path: numericCoordinates,
geodesic: false,
strokeColor: '#1A73E8',
strokeOpacity: 1.0,
strokeWeight: 4
});
route.setMap(map);
// auto fit
const bounds = new window.google.maps.LatLngBounds();
numericCoordinates.forEach((p) => bounds.extend(p));
map.fitBounds(bounds);
};
return (
<>
<button
onClick={() => setMapOpen(false)}
style={{
position: 'absolute',
top: 10,
right: 10,
zIndex: 999,
padding: '6px 12px',
background: '#1A73E8',
color: 'white',
borderRadius: 6,
cursor: 'pointer',
border: 'none'
}}
>
Close
</button>
<LoadScriptNext googleMapsApiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
<GoogleMap mapContainerStyle={containerStyle} center={start} zoom={14} onLoad={onMapLoad}>
{/* Polyline and markers added via onLoad */}
</GoogleMap>
</LoadScriptNext>
</>
);
};
export default MapWithRouteGoogle;

View File

@@ -1,76 +1,55 @@
import { Button } from '@mui/material';
import { LoadScriptNext, GoogleMap, Marker, OverlayView } from '@react-google-maps/api';
import { MapContainer, TileLayer, Marker, Tooltip } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
const containerStyle = {
width: '100%',
height: 'calc(100vh - 150px)'
};
// Green marker
const GreenIcon = new L.Icon({
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
shadowSize: [41, 41]
});
// Red marker
const RedIcon = new L.Icon({
iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
shadowSize: [41, 41]
});
export default function RiderLocationMap({ riderLocations }) {
console.log('riderLocations', riderLocations);
const center = {
lat: Number(riderLocations?.[0]?.latitude || 11.0056),
lng: Number(riderLocations?.[0]?.longitude || 76.9661)
};
const GreenIcon = {
url: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
scaledSize: new window.google.maps.Size(25, 41),
anchor: new window.google.maps.Point(12, 41)
};
const RedIcon = {
url: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
scaledSize: new window.google.maps.Size(25, 41),
anchor: new window.google.maps.Point(12, 41)
};
const center = [Number(riderLocations?.[0]?.latitude || 11.0056), Number(riderLocations?.[0]?.longitude || 76.9661)];
return (
<LoadScriptNext googleMapsApiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}>
<GoogleMap mapContainerStyle={containerStyle} zoom={12} center={center}>
{riderLocations &&
riderLocations?.map((r, index) => {
const lat = Number(r.latitude);
const lng = Number(r.longitude);
return (
<div key={index}>
{/* Marker */}
<Marker
position={{ lat, lng }}
icon={r.status == 'active' ? GreenIcon : RedIcon}
label={{
fontSize: '14px',
fontWeight: 'bold'
}}
/>
<OverlayView position={{ lat, lng }} mapPaneName={OverlayView.OVERLAY_LAYER}>
<div
style={{
background: 'none',
color: 'green',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: 600,
whiteSpace: 'nowrap',
transform: 'translate(-50%, -140%)',
pointerEvents: 'none',
ml: 20
}}
>
<Button variant="contained" color="primary" size="small">
{` ${r.username} `}
{/* <br /> */}
{/* {`${r.contactno || '##### ##### '} `} */}
<br />
{`(${r.orderid || ''}) `}
</Button>
</div>
</OverlayView>
</div>
);
})}
</GoogleMap>
</LoadScriptNext>
<MapContainer center={center} zoom={12} style={containerStyle}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution="&copy; OpenStreetMap contributors" />
{riderLocations &&
riderLocations.map((r, index) => {
const lat = Number(r.latitude);
const lng = Number(r.longitude);
return (
<Marker key={index} position={[lat, lng]} icon={r.status == 'active' ? GreenIcon : RedIcon}>
<Tooltip permanent direction="top" offset={[0, -35]} opacity={1} className="rider-location-tooltip">
<Button variant="contained" color="primary" size="small">
{` ${r.username} `}
<br />
{`(${r.orderid || ''}) `}
</Button>
</Tooltip>
</Marker>
);
})}
</MapContainer>
);
}

View File

@@ -1,10 +1,46 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { GoogleMap, Polyline, Marker, InfoWindow, useJsApiLoader } from '@react-google-maps/api';
import React, { useEffect, useMemo, useState } from 'react';
import { MapContainer, TileLayer, Polyline, Marker, Popup, useMap } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { Box, IconButton, Stack, Typography, CircularProgress } from '@mui/material';
import { MdClose, MdRoute } from 'react-icons/md';
const containerStyle = { width: '100%', height: '100%' };
// Numbered step icon — brand purple to match the planned-route polyline below.
// Drawn fresh per render as a data URL so the step number can be baked into
// the SVG without juggling external marker assets.
const stepIcon = (n, isFocused) => {
const size = isFocused ? 38 : 32;
const color = isFocused ? '#4D1C61' : '#662582';
const svg = encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="${size}" height="${size}">` +
`<circle cx="16" cy="16" r="14" fill="${color}" stroke="white" stroke-width="3"/>` +
`<text x="16" y="21" text-anchor="middle" font-family="Arial,sans-serif" font-size="14" font-weight="700" fill="white">${n}</text>` +
`</svg>`
);
return new L.Icon({
iconUrl: `data:image/svg+xml;charset=UTF-8,${svg}`,
iconSize: [size, size],
iconAnchor: [size / 2, size / 2]
});
};
// Fits the map to the planned path once both the map and data are ready.
// Re-runs whenever the route changes (different rider / date).
const FitBoundsController = ({ dropPath }) => {
const map = useMap();
useEffect(() => {
if (!dropPath.length) return;
if (dropPath.length === 1) {
map.setView(dropPath[0], 14);
} else {
map.fitBounds(dropPath, { padding: [48, 48] });
}
}, [dropPath, map]);
return null;
};
// Renders a single rider's PLANNED route for the date range chosen on the
// Riders Summary page. `details` is an ordered array of waypoints (sorted by
// the planning step number) shaped as:
@@ -13,84 +49,38 @@ const containerStyle = { width: '100%', height: '100%' };
// `dropLat/dropLng` are required; pickup coords are optional and rendered as
// faded pre-stops if present.
export default function RidersRoutes({ details, loading, riderName, dateRange, onClose }) {
const mapRef = useRef(null);
const [focusedStep, setFocusedStep] = useState(null);
const [routePath, setRoutePath] = useState([]);
const [routeLoading, setRouteLoading] = useState(false);
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_KEY
});
// Step-pin coordinates in planning order — what the polyline connects.
const dropPath = useMemo(
() => (details || []).map((d) => ({ lat: d.dropLat, lng: d.dropLng })),
[details]
);
// Auto-fit map bounds to the full planned path once the map and data are
// both ready. Re-runs whenever the route changes (different rider / date).
useEffect(() => {
if (!isLoaded || !mapRef.current || dropPath.length === 0) return;
const bounds = new window.google.maps.LatLngBounds();
dropPath.forEach((p) => bounds.extend(p));
mapRef.current.fitBounds(bounds, 48);
}, [isLoaded, dropPath]);
const dropPath = useMemo(() => (details || []).map((d) => [d.dropLat, d.dropLng]), [details]);
// Resolve the rider's planned waypoints into an actual road-following path
// via the Directions API. Without this, the polyline would cut across
// buildings / aerial lines — operators have no way to read the real route.
// Directions has a 25-waypoint limit per request, so we chunk and stitch.
// via OSRM. Without this, the polyline would cut across buildings / aerial
// lines — operators have no way to read the real route.
useEffect(() => {
if (!isLoaded || dropPath.length < 2) {
if (dropPath.length < 2) {
setRoutePath([]);
return;
}
let cancelled = false;
const ds = new window.google.maps.DirectionsService();
const MAX_WPS = 23; // origin + 23 waypoints + destination = 25 stops/chunk
const fetchSegment = (origin, destination, waypoints) =>
new Promise((resolve, reject) => {
ds.route(
{
origin,
destination,
waypoints: waypoints.map((p) => ({ location: p, stopover: true })),
travelMode: window.google.maps.TravelMode.DRIVING
},
(result, status) => {
if (status === 'OK') resolve(result);
else reject(new Error(status));
}
);
});
(async () => {
setRouteLoading(true);
try {
const points = dropPath;
const all = [];
let i = 0;
while (i < points.length - 1) {
const remaining = points.length - 1 - i;
const take = Math.min(remaining, MAX_WPS + 1);
const origin = points[i];
const destination = points[i + take];
const waypoints = points.slice(i + 1, i + take);
const res = await fetchSegment(origin, destination, waypoints);
const seg = res.routes[0].overview_path.map((ll) => ({
lat: ll.lat(),
lng: ll.lng()
}));
// Avoid duplicating the join point between adjacent chunks.
if (all.length > 0 && seg.length > 0) seg.shift();
all.push(...seg);
i += take;
const coords = dropPath.map(([lat, lng]) => `${lng},${lat}`).join(';');
const url = `https://router.project-osrm.org/route/v1/driving/${coords}?overview=full&geometries=geojson`;
const res = await fetch(url);
const data = await res.json();
if (!cancelled && data.routes?.length) {
const points = data.routes[0].geometry.coordinates.map(([lng, lat]) => [lat, lng]);
setRoutePath(points);
} else if (!cancelled) {
setRoutePath([]);
}
if (!cancelled) setRoutePath(all);
} catch {
// Fall back to the straight-line skeleton on failure (quota, no route, etc.).
} catch (e) {
console.warn('OSRM route error:', e);
if (!cancelled) setRoutePath([]);
} finally {
if (!cancelled) setRouteLoading(false);
@@ -100,22 +90,7 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
return () => {
cancelled = true;
};
}, [isLoaded, dropPath]);
// Numbered step icon as a data URL — drawn fresh per render so we can pass
// the step number into the SVG without juggling external assets. Color is
// brand purple to match the planned-route polyline below.
const stepIcon = (n, isFocused) => {
const size = isFocused ? 38 : 32;
const color = isFocused ? '#4D1C61' : '#662582';
const svg = encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="${size}" height="${size}">` +
`<circle cx="16" cy="16" r="14" fill="${color}" stroke="white" stroke-width="3"/>` +
`<text x="16" y="21" text-anchor="middle" font-family="Arial,sans-serif" font-size="14" font-weight="700" fill="white">${n}</text>` +
`</svg>`
);
return `data:image/svg+xml;charset=UTF-8,${svg}`;
};
}, [dropPath]);
const headerBar = (
<Stack
@@ -133,12 +108,8 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
>
<MdRoute size={20} />
<Stack sx={{ flex: 1, minWidth: 0 }}>
<Typography sx={{ fontWeight: 700, fontSize: 15, lineHeight: 1.2 }}>
Planned route{riderName ? `${riderName}` : ''}
</Typography>
{dateRange && (
<Typography sx={{ fontSize: 12, opacity: 0.85 }}>{dateRange}</Typography>
)}
<Typography sx={{ fontWeight: 700, fontSize: 15, lineHeight: 1.2 }}>Planned route{riderName ? `${riderName}` : ''}</Typography>
{dateRange && <Typography sx={{ fontSize: 12, opacity: 0.85 }}>{dateRange}</Typography>}
</Stack>
{details && details.length > 0 && (
<Typography sx={{ fontSize: 12, opacity: 0.9, fontWeight: 600 }}>
@@ -154,16 +125,14 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
</Stack>
);
// Loading state — route fetch in flight OR Google Maps script not ready yet.
if (loading || !isLoaded) {
// Loading state — parent is still fetching the planned route data.
if (loading) {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{headerBar}
<Stack alignItems="center" justifyContent="center" sx={{ flex: 1, gap: 1.5 }}>
<CircularProgress size={32} />
<Typography sx={{ color: '#64748b', fontSize: 13 }}>
{loading ? 'Loading planned route…' : 'Loading map…'}
</Typography>
<Typography sx={{ color: '#64748b', fontSize: 13 }}>Loading planned route</Typography>
</Stack>
</Box>
);
@@ -176,9 +145,7 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{headerBar}
<Stack alignItems="center" justifyContent="center" sx={{ flex: 1, gap: 1, p: 3 }}>
<Typography sx={{ color: '#1e293b', fontWeight: 700, fontSize: 16 }}>
No planned route for this rider
</Typography>
<Typography sx={{ color: '#1e293b', fontWeight: 700, fontSize: 16 }}>No planned route for this rider</Typography>
<Typography sx={{ color: '#64748b', fontSize: 13, textAlign: 'center', maxWidth: 360 }}>
There are no deliveries with drop coordinates assigned to this rider for the selected date range.
</Typography>
@@ -191,53 +158,21 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{headerBar}
<Box sx={{ flex: 1, minHeight: 0 }}>
<GoogleMap
mapContainerStyle={containerStyle}
onLoad={(map) => (mapRef.current = map)}
center={dropPath[0]}
zoom={14}
options={{
streetViewControl: false,
mapTypeControl: false,
fullscreenControl: false
}}
>
<MapContainer center={dropPath[0]} zoom={14} style={containerStyle} zoomControl={false}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution="&copy; OpenStreetMap contributors" />
<FitBoundsController dropPath={dropPath} />
{routePath.length > 0 ? (
<>
{/* Translucent backdrop so the route stays legible on busy tiles. */}
<Polyline
path={routePath}
options={{ strokeColor: '#662582', strokeOpacity: 0.25, strokeWeight: 8 }}
/>
{/* Road-following planned route from the Directions API. */}
<Polyline
path={routePath}
options={{ strokeColor: '#662582', strokeOpacity: 0.95, strokeWeight: 4 }}
/>
<Polyline positions={routePath} pathOptions={{ color: '#662582', opacity: 0.25, weight: 8 }} />
{/* Road-following planned route from OSRM. */}
<Polyline positions={routePath} pathOptions={{ color: '#662582', opacity: 0.95, weight: 4 }} />
</>
) : (
// Fallback while Directions is in flight (or if it fails) — dashed
// Fallback while OSRM is in flight (or if it fails) — dashed
// straight-line skeleton between drop pins in step order.
<Polyline
path={dropPath}
options={{
strokeColor: '#662582',
strokeOpacity: 0,
strokeWeight: 0,
icons: [
{
icon: {
path: 'M 0,-1 0,1',
strokeOpacity: 0.6,
strokeColor: '#662582',
scale: 3
},
offset: '0',
repeat: '14px'
}
]
}}
/>
<Polyline positions={dropPath} pathOptions={{ color: '#662582', opacity: 0.6, weight: 3, dashArray: '2 10', lineCap: 'round' }} />
)}
{details.map((d, i) => {
@@ -246,39 +181,29 @@ export default function RidersRoutes({ details, loading, riderName, dateRange, o
return (
<Marker
key={`step-${d.deliveryid || d.orderid || i}`}
position={{ lat: d.dropLat, lng: d.dropLng }}
icon={{ url: stepIcon(stepNum, isFocused) }}
onClick={() => setFocusedStep(isFocused ? null : d.deliveryid)}
zIndex={isFocused ? 1000 : stepNum}
position={[d.dropLat, d.dropLng]}
icon={stepIcon(stepNum, isFocused)}
eventHandlers={{
click: () => setFocusedStep(isFocused ? null : d.deliveryid)
}}
zIndexOffset={isFocused ? 1000 : stepNum}
>
{isFocused && (
<InfoWindow onCloseClick={() => setFocusedStep(null)}>
<Box sx={{ minWidth: 180, fontFamily: 'inherit' }}>
<Typography sx={{ fontWeight: 800, fontSize: 13, color: '#0f172a' }}>
Step {stepNum} · {d.customer}
</Typography>
{d.address && (
<Typography sx={{ fontSize: 12, color: '#475569', mt: 0.5 }}>
{d.address}
</Typography>
)}
{d.expectedTime && (
<Typography sx={{ fontSize: 12, color: '#64748b', mt: 0.5 }}>
ETA {String(d.expectedTime).slice(11, 16) || d.expectedTime}
</Typography>
)}
{d.orderid && (
<Typography sx={{ fontSize: 11, color: '#94a3b8', mt: 0.5 }}>
Order #{d.orderid}
</Typography>
)}
</Box>
</InfoWindow>
)}
<Popup onClose={() => setFocusedStep(null)}>
<Box sx={{ minWidth: 180, fontFamily: 'inherit' }}>
<Typography sx={{ fontWeight: 800, fontSize: 13, color: '#0f172a' }}>
Step {stepNum} · {d.customer}
</Typography>
{d.address && <Typography sx={{ fontSize: 12, color: '#475569', mt: 0.5 }}>{d.address}</Typography>}
{d.expectedTime && (
<Typography sx={{ fontSize: 12, color: '#64748b', mt: 0.5 }}>ETA {String(d.expectedTime).slice(11, 16) || d.expectedTime}</Typography>
)}
{d.orderid && <Typography sx={{ fontSize: 11, color: '#94a3b8', mt: 0.5 }}>Order #{d.orderid}</Typography>}
</Box>
</Popup>
</Marker>
);
})}
</GoogleMap>
</MapContainer>
</Box>
</Box>
);

View File

@@ -50,7 +50,7 @@ import { useState, useEffect } from 'react';
import axios from 'axios';
import Loader from 'components/Loader';
import Transitions from 'components/@extended/Transitions';
import Autocomplete from 'react-google-autocomplete';
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
import * as React from 'react';
@@ -60,8 +60,6 @@ import TablePagination from '@mui/material/TablePagination';
import TableSortLabel from '@mui/material/TableSortLabel';
import { visuallyHidden } from '@mui/utils';
import Geocode from 'react-geocode';
const Requests = () => {
// let dispatch = useDispatch();
@@ -201,7 +199,6 @@ const Requests = () => {
const [suburb, setSuburb] = useState('');
const [currenttenantid] = useState('');
const [latlong, setLatlong] = useState({});
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
// const [alertmessage, setAlertmessage] = useState('');
// const [toast, setToast] = useState(false);
const [rolesarr, setRolesarr] = useState([]);
@@ -277,27 +274,34 @@ const Requests = () => {
// }
useEffect(() => {
try {
Geocode.fromAddress(address).then(
(response) => {
if (response.status == 'OK') {
const { lat, lng } = response.results[0].geometry.location;
setLatlong({
lat,
lng
});
console.log(response);
}
},
(error) => {
console.log(error);
const handleAddressPlaceSelected = (place) => {
setAddress(place.formatted_address);
setLatlong({ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() });
let city1, state, zipcode1, suburb1;
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'locality':
city1 = component.long_name;
break;
case 'administrative_area_level_1':
state = component.long_name;
break;
case 'postal_code':
zipcode1 = component.long_name;
break;
case 'sublocality':
case 'sublocality_level_1':
suburb1 = component.long_name;
break;
}
);
} catch (err) {
console.log(err);
}
}, [address]);
});
});
setCity(city1 || '');
setState1(state || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
};
useEffect(() => {
console.log('rolesarr');
@@ -1458,53 +1462,22 @@ const Requests = () => {
{/* } */}
<Autocomplete
className="automap"
apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}
style={{
width: '100%',
height: '40px',
borderRadius: '5px',
border: '1px solid #e0e0e0',
textIndent: '10px',
outline: 'none'
// ':hover': {
// border: '1px solid #00b0ff !important',
// backgroundColor:'blue'
// }
}}
onPlaceSelected={(place) => {
setAddress(place.formatted_address);
let city1, state, zipcode1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state = place.address_components[i].long_name;
break;
case 'postal_code':
zipcode1 = place.address_components[i].long_name;
break;
case 'sublocality':
suburb1 = place.address_components[i].long_name;
break;
}
}
}
setCity(city1 || '');
setState1(state || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
}}
options={{
types: ['address' || 'geocode']
}}
<AddressAutocomplete
id="request-address-autocomplete"
fullWidth
placeholder="Address"
value={address}
onChange={(e) => setAddress(e.target.value)}
onChange={setAddress}
onPlaceSelected={handleAddressPlaceSelected}
TextFieldProps={{
className: 'automap',
InputProps: {
style: {
borderRadius: '5px',
border: '1px solid #e0e0e0'
}
}
}}
/>
</Stack>
</Grid>

View File

@@ -11,12 +11,10 @@ import { Box, Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typo
import MainCard from 'components/MainCard';
import axios from 'axios';
// assets
import { usePlacesWidget } from 'react-google-autocomplete';
import AddressAutocomplete, { geocodeAddress } from 'components/nearle_components/AddressAutocomplete';
import Loader from 'components/Loader';
import Geocode from 'react-geocode';
import { enqueueSnackbar } from 'notistack';
import { useNavigate } from 'react-router';
// import { setLocationType } from 'react-geocode';
// const avatarImage = require.context('assets/images/users', true);
@@ -50,9 +48,6 @@ const Createrider = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
// Geocode.setApiKey('AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8');
const [loading, setLoading] = useState(false);
useEffect(() => {
@@ -64,25 +59,15 @@ const Createrider = () => {
}, []);
useEffect(() => {
try {
Geocode.fromAddress(address).then(
(response) => {
if (response.status == 'OK') {
const { lat, lng } = response.results[0].geometry.location;
setLatlong({
lat,
lng
});
console.log(response);
}
},
(error) => {
console.log(error);
}
);
} catch (err) {
console.log(err);
}
let active = true;
geocodeAddress(address).then((place) => {
if (active && place) {
setLatlong({ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() });
}
});
return () => {
active = false;
};
}, [address]);
const opentoast = (message) => {
@@ -119,45 +104,33 @@ const Createrider = () => {
}
}, [selectedImage]);
const { ref: materialRef } = usePlacesWidget({
apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
onPlaceSelected: (place) => {
console.log(place);
setAddress(place.formatted_address);
let city1, zipcode1, state1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state1 = place.address_components[i].long_name;
break;
case 'postal_code':
zipcode1 = place.address_components[i].long_name;
break;
case 'sublocality':
suburb1 = place.address_components[i].long_name;
break;
}
const handleAddressPlaceSelected = (place) => {
setAddress(place.formatted_address);
let city1, zipcode1, state1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state1 = place.address_components[i].long_name;
break;
case 'postal_code':
zipcode1 = place.address_components[i].long_name;
break;
case 'sublocality':
case 'sublocality_level_1':
suburb1 = place.address_components[i].long_name;
break;
}
}
setCity(city1 || '');
setState(state1 || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
// setAddress(place.formatted_address)
},
// inputAutocompleteValue: "country",
options: {
// componentRestrictions: 'us',
// types: ["establishment"]
types: ['address' || 'geocode']
}
});
setCity(city1 || '');
setState(state1 || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
};
const createprofile = async () => {
console.log('res', businessname, businessno, mobilenumber, emailaddress, address, city, zipcode);
@@ -340,14 +313,13 @@ const Createrider = () => {
<Grid item xs={12}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-address">Address</InputLabel>
<TextField
fullWidth
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
<AddressAutocomplete
id="personal-address"
fullWidth
placeholder="Address"
value={address}
onChange={(e) => setAddress(e.target.value)}
inputRef={materialRef}
onChange={setAddress}
onPlaceSelected={handleAddressPlaceSelected}
/>
</Stack>
</Grid>

View File

@@ -28,9 +28,8 @@ import { DatePicker } from '@mui/x-date-pickers/DatePicker';
// project import
import MainCard from 'components/MainCard';
import axios from 'axios';
import { usePlacesWidget } from 'react-google-autocomplete';
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
import Loader from 'components/Loader';
import Geocode from 'react-geocode';
import { enqueueSnackbar } from 'notistack';
import dayjs from 'dayjs';
import CircularLoader from 'components/CircularLoader';
@@ -55,8 +54,6 @@ const EditRider = () => {
const [locaName, setLocoName] = useState();
const userid = localStorage.getItem('userid');
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
const [loading, setLoading] = useState(false);
const fetchRiderData = async (id) => {
@@ -237,46 +234,30 @@ const EditRider = () => {
}
}, [selectedImage]);
const { ref: materialRef } = usePlacesWidget({
apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
onPlaceSelected: (place) => {
console.log(place);
setAddress(place.formatted_address);
const handleAddressPlaceSelected = (place) => {
setAddress(place.formatted_address);
let city1, zipcode1, state1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state1 = place.address_components[i].long_name;
break;
case 'postal_code':
zipcode1 = place.address_components[i].long_name;
break;
case 'sublocality':
suburb1 = place.address_components[i].long_name;
break;
}
let city1, state1, suburb1;
for (let i = 0; i < place.address_components.length; i++) {
for (let j = 0; j < place.address_components[i].types.length; j++) {
switch (place.address_components[i].types[j]) {
case 'locality':
city1 = place.address_components[i].long_name;
break;
case 'administrative_area_level_1':
state1 = place.address_components[i].long_name;
break;
case 'sublocality':
case 'sublocality_level_1':
suburb1 = place.address_components[i].long_name;
break;
}
}
setCity(city1 || '');
setState(state1 || '');
setZipcode(zipcode1 || '');
setSuburb(suburb1 || '');
// setAddress(place.formatted_address)
},
// inputAutocompleteValue: "country",
options: {
// componentRestrictions: 'us',
// types: ["establishment"]
types: ['address' || 'geocode']
}
});
setCity(city1 || '');
setState(state1 || '');
setSuburb(suburb1 || '');
};
const updateRider = async () => {
setLoading(true);
@@ -483,16 +464,13 @@ const EditRider = () => {
<Grid item xs={12}>
<Stack spacing={1.25}>
<InputLabel htmlFor="personal-address">Address</InputLabel>
<TextField
fullWidth
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
<AddressAutocomplete
id="personal-address"
fullWidth
placeholder="Address"
value={address}
onChange={(e) => {
setAddress(e.target.value);
}}
inputRef={materialRef}
onChange={setAddress}
onPlaceSelected={handleAddressPlaceSelected}
/>
</Stack>
</Grid>

View File

@@ -1,6 +1,5 @@
import * as React from 'react';
import { useState, useEffect, useRef, Fragment } from 'react';
import Geocode from 'react-geocode';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import {
@@ -491,8 +490,6 @@ const Riders = () => {
}
});
Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY);
const handleChangetab = (i) => {
setTabvalue(i);
setLogsRow(null);