back icon in new address
This commit is contained in:
@@ -13,6 +13,7 @@ import { useAddressForm } from './hooks/useAddressForm';
|
||||
import { fetchAddressFromCoordinates } from './utils/fetchAddress';
|
||||
import { useGetAddressById } from '../hooks/useAddressData';
|
||||
import { useGetAbout } from '@/app/[name]/(Main)/about/hooks/useAboutData';
|
||||
import { ActiveSearchbox } from '@/components/input/ActiveSearchbox';
|
||||
|
||||
const CustomMap = dynamic(
|
||||
() => import('@/components/map/CustomMap'),
|
||||
@@ -34,6 +35,10 @@ function OrderTrackingPage() {
|
||||
const [selectedAddress, setSelectedAddress] = useState<NominatimReverseGeocodingResponse | null>(null);
|
||||
const [markers, setMarkers] = useState<MarkerData[]>([]);
|
||||
const [initialPositionSet, setInitialPositionSet] = useState(false);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [searchResults, setSearchResults] = useState<Array<{ id: string; title: string; position: [number, number] }>>([]);
|
||||
const [mapInstance, setMapInstance] = useState<L.Map | null>(null);
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
|
||||
const restaurant = aboutData?.data;
|
||||
|
||||
@@ -158,6 +163,72 @@ function OrderTrackingPage() {
|
||||
setSelectedAddress(null);
|
||||
};
|
||||
|
||||
const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout>();
|
||||
|
||||
const handleSearch = async (value: string) => {
|
||||
if (!value || value.trim().length === 0) {
|
||||
setSearchResults([]);
|
||||
setIsSearchOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
setIsSearching(true);
|
||||
setIsSearchOpen(true);
|
||||
|
||||
const timeout = setTimeout(async () => {
|
||||
try {
|
||||
const viewbox = '44.0,25.0,63.0,40.0';
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/search?` +
|
||||
`format=json&` +
|
||||
`q=${encodeURIComponent(value)}&` +
|
||||
`countrycodes=ir&` +
|
||||
`viewbox=${viewbox}&` +
|
||||
`bounded=0&` +
|
||||
`limit=10&` +
|
||||
`namedetails=1&` +
|
||||
`accept-language=fa`
|
||||
, {
|
||||
headers: {
|
||||
'Accept-Language': 'fa,en;q=0.9'
|
||||
}
|
||||
});
|
||||
interface SearchResult {
|
||||
place_id: string;
|
||||
display_name: string;
|
||||
lat: string;
|
||||
lon: string;
|
||||
}
|
||||
const data: SearchResult[] = await response.json();
|
||||
setSearchResults(data.map((item) => ({
|
||||
id: item.place_id,
|
||||
title: item.display_name,
|
||||
position: [parseFloat(item.lat), parseFloat(item.lon)] as [number, number]
|
||||
})));
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
setSearchResults([]);
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
setSearchTimeout(timeout);
|
||||
};
|
||||
|
||||
const handleSearchResultSelect = (result: { id: string; title: string; position: [number, number] }) => {
|
||||
setSelectedPosition(result.position);
|
||||
setMapCenter(result.position);
|
||||
setIsSearchOpen(false);
|
||||
if (mapInstance) {
|
||||
mapInstance.setView(result.position, mapInstance.getZoom());
|
||||
}
|
||||
};
|
||||
|
||||
if (editMode && isLoadingAddress) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-gray-50 flex items-center justify-center">
|
||||
@@ -185,20 +256,57 @@ function OrderTrackingPage() {
|
||||
onPositionSelect={handlePositionSelect}
|
||||
onZoomChange={handleZoomChange}
|
||||
onClick={handleMapClick}
|
||||
searchEnabled={false}
|
||||
onMapReady={setMapInstance}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute top-4 right-4 z-1000">
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="p-2 rounded-full bg-container/90 backdrop-blur-sm shadow-sm"
|
||||
>
|
||||
<ArrowLeft size={24} className="stroke-foreground" />
|
||||
</button>
|
||||
<div className="absolute top-4 left-4 right-4 z-1001">
|
||||
<div className="flex items-start gap-2" dir="ltr">
|
||||
{!isSearchOpen && (
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="p-2 rounded-full bg-container/90 backdrop-blur-sm shadow-sm shrink-0"
|
||||
>
|
||||
<ArrowLeft size={24} className="stroke-foreground" />
|
||||
</button>
|
||||
)}
|
||||
<div className="flex-1 min-w-0 relative">
|
||||
<div className="[&_.map-search-container]:static! [&_.map-search-container]:transform-none! [&_.map-search-container]:w-full! [&_.map-search-container]:max-w-none! [&_.map-search-container]:top-0!" dir="rtl">
|
||||
<ActiveSearchbox
|
||||
placeholder="جستجو"
|
||||
onSearch={handleSearch}
|
||||
isLoading={isSearching}
|
||||
results={searchResults}
|
||||
onResultSelect={(v) => {
|
||||
const result = searchResults.find(r => r.id === v.id && r.title === v.title);
|
||||
if (result) {
|
||||
handleSearchResultSelect(result);
|
||||
}
|
||||
}}
|
||||
onFocus={() => setIsSearchOpen(true)}
|
||||
className="[&_div]:h-9! [&_input]:text-xs! [&_div]:px-3! [&_input]:px-2! [&_div]:dir-rtl! [&_input]:text-right! [&_input]:dir-rtl!"
|
||||
/>
|
||||
</div>
|
||||
{isSearchOpen && (isSearching || searchResults.length > 0) && (
|
||||
<div className="mt-2 flex items-start gap-2" dir="ltr">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsSearchOpen(false);
|
||||
setSearchResults([]);
|
||||
}}
|
||||
className="p-2 rounded-full bg-container/90 backdrop-blur-sm shadow-sm shrink-0 mt-1"
|
||||
>
|
||||
<ArrowLeft size={24} className="stroke-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasServiceArea && (
|
||||
<div className="absolute top-4 left-4 z-1000 max-w-sm">
|
||||
<div className="absolute bottom-4 left-4 z-1000 max-w-sm">
|
||||
<div className="bg-blue-50/90 border border-blue-200 rounded-lg p-3 shadow-sm">
|
||||
<p className="text-xs text-blue-800">
|
||||
محدوده سرویسدهی روی نقشه با رنگ آبی مشخص شده است
|
||||
|
||||
@@ -28,6 +28,7 @@ interface CustomMapProps {
|
||||
onClick?: (event: L.LeafletMouseEvent) => void;
|
||||
searchEnabled?: boolean
|
||||
markerActive?: boolean
|
||||
onMapReady?: (map: L.Map) => void;
|
||||
}
|
||||
|
||||
// Component to handle map events and position updates
|
||||
@@ -114,6 +115,7 @@ const CustomMap: React.FC<CustomMapProps> = ({
|
||||
onClick,
|
||||
markerActive = true,
|
||||
searchEnabled = true,
|
||||
onMapReady,
|
||||
}) => {
|
||||
const [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null);
|
||||
const [mapInstance, setMapInstance] = useState<L.Map | null>(null);
|
||||
@@ -136,6 +138,13 @@ const CustomMap: React.FC<CustomMapProps> = ({
|
||||
checkTiles();
|
||||
}, [mapInstance]);
|
||||
|
||||
// Notify parent when map is ready
|
||||
useEffect(() => {
|
||||
if (mapInstance && onMapReady) {
|
||||
onMapReady(mapInstance);
|
||||
}
|
||||
}, [mapInstance, onMapReady]);
|
||||
|
||||
// Initialize default icon on client side only
|
||||
useEffect(() => {
|
||||
// Dynamic import of Leaflet on the client side
|
||||
|
||||
Reference in New Issue
Block a user