updates on the ui changes and changed into doormile

This commit is contained in:
2026-08-24 18:10:47 +05:30
parent dcf9770ada
commit aa42d9ee81
409 changed files with 49950 additions and 66532 deletions

View File

@@ -0,0 +1,197 @@
import PropTypes from 'prop-types';
import React, { useEffect, useRef, useState } from 'react';
const useDebouncedCallback = (callback, delay) => { const timeoutRef = React.useRef(null); return React.useCallback((...args) => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } timeoutRef.current = setTimeout(() => { callback(...args); }, delay); }, [callback, delay]); };
const NOMINATIM_SEARCH_URL = 'https://nominatim.openstreetmap.org/search';
const toPlace = (nom) => {
const c = nom.address || {};
const addr = [
{ long_name: c.house_number || '', short_name: c.house_number || '', types: ['street_number'] },
{ long_name: c.road || '', short_name: c.road || '', types: ['route'] },
{ long_name: c.city || c.town || c.village || '', short_name: c.city || c.town || c.village || '', types: ['locality', 'political'] },
{ long_name: c.county || c.state_district || '', short_name: c.county || c.state_district || '', types: ['administrative_area_level_2', 'political'] },
{ long_name: c.state || '', short_name: c.state || '', types: ['administrative_area_level_1', 'political'] },
{ long_name: c.country || '', short_name: c.country_code?.toUpperCase() || '', types: ['country', 'political'] },
{ long_name: c.postcode || '', short_name: c.postcode || '', types: ['postal_code'] }
].filter((p) => p.long_name);
return {
address_components: addr,
formatted_address: nom.display_name,
geometry: {
location: {
lat: () => Number(nom.lat),
lng: () => Number(nom.lon)
}
}
};
};
const AddressAutocomplete = ({
id,
label,
placeholder,
value,
onChange,
onPlaceSelected,
bias,
fullWidth = true,
disabled = false,
status
}) => {
const [inputValue, setInputValue] = useState(value || '');
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const wrapRef = useRef(null);
const abortCtrl = useRef(null);
useEffect(() => {
setInputValue(value || '');
}, [value]);
const biasKey = bias ? `${bias.lat},${bias.lng}` : '';
const runSearch = useDebouncedCallback(async (query) => {
if (!query || query.length < 3) {
setOptions([]);
setIsOpen(false);
setLoading(false);
return;
}
if (abortCtrl.current) abortCtrl.current.abort();
abortCtrl.current = new AbortController();
try {
setLoading(true);
const url = new URL(NOMINATIM_SEARCH_URL);
url.searchParams.set('q', query);
url.searchParams.set('format', 'jsonv2');
url.searchParams.set('addressdetails', '1');
url.searchParams.set('limit', '5');
if (bias && bias.lat && bias.lng) {
url.searchParams.set('lat', bias.lat);
url.searchParams.set('lon', bias.lng);
}
const res = await fetch(url.toString(), {
headers: { 'Accept-Language': 'en-GB,en;q=0.9' },
signal: abortCtrl.current.signal
});
if (!res.ok) throw new Error('Search failed');
const data = await res.json();
setOptions(data || []);
setIsOpen((data || []).length > 0);
} catch (e) {
if (e.name !== 'AbortError') console.error('Nominatim search error:', e);
} finally {
setLoading(false);
}
}, 500);
useEffect(() => {
if (inputValue && inputValue !== value) runSearch(inputValue);
}, [inputValue, biasKey]);
useEffect(() => {
const onDocDown = (e) => {
if (wrapRef.current && !wrapRef.current.contains(e.target)) setIsOpen(false);
};
document.addEventListener('mousedown', onDocDown);
return () => document.removeEventListener('mousedown', onDocDown);
}, []);
const pick = (option) => {
setInputValue(option.display_name || '');
onChange?.(option.display_name || '');
onPlaceSelected?.(toPlace(option));
setIsOpen(false);
};
return (
<div ref={wrapRef} className="relative" style={{ width: fullWidth ? '100%' : undefined }}>
{label && <label htmlFor={id} className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
<div className="relative">
<input
id={id}
type="text"
placeholder={placeholder}
value={inputValue}
disabled={disabled}
onChange={(e) => {
setInputValue(e.target.value);
onChange?.(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === 'Escape') setIsOpen(false);
}}
className="flex h-10 w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-400 disabled:cursor-not-allowed disabled:opacity-50"
/>
{loading && (
<span className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<span className="w-4 h-4 rounded-full border-2 border-t-2 border-gray-200 border-t-blue-500 animate-spin"></span>
</span>
)}
</div>
{isOpen && options.length > 0 && (
<ul className="absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg max-h-60 overflow-y-auto" role="listbox">
{options.map((option) => (
<li key={option.place_id}>
<button
type="button"
onClick={() => pick(option)}
className="w-full text-left px-3 py-2 text-sm hover:bg-gray-100 focus:bg-gray-100 focus:outline-none"
>
<span className="block truncate">{option.display_name}</span>
</button>
</li>
))}
</ul>
)}
</div>
);
};
AddressAutocomplete.propTypes = {
id: PropTypes.string,
label: PropTypes.string,
placeholder: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
onPlaceSelected: PropTypes.func,
bias: PropTypes.object,
fullWidth: PropTypes.bool,
disabled: PropTypes.bool,
status: PropTypes.object
};
export default AddressAutocomplete;
export async function geocodeAddress(address, { bias } = {}) {
const url = new URL(NOMINATIM_SEARCH_URL);
url.searchParams.set('q', address);
url.searchParams.set('format', 'jsonv2');
url.searchParams.set('addressdetails', '1');
url.searchParams.set('limit', '1');
if (bias && bias.lat && bias.lng) {
url.searchParams.set('lat', bias.lat);
url.searchParams.set('lon', bias.lng);
}
try {
const res = await fetch(url.toString(), { headers: { 'Accept-Language': 'en-GB,en;q=0.9' } });
if (!res.ok) throw new Error('Search failed');
const data = await res.json();
if (data && data.length > 0) return toPlace(data[0]);
return null;
} catch (err) {
console.error('geocodeAddress error:', err);
return null;
}
}