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 { fetchAddressFromCoordinates } from './utils/fetchAddress';
|
||||||
import { useGetAddressById } from '../hooks/useAddressData';
|
import { useGetAddressById } from '../hooks/useAddressData';
|
||||||
import { useGetAbout } from '@/app/[name]/(Main)/about/hooks/useAboutData';
|
import { useGetAbout } from '@/app/[name]/(Main)/about/hooks/useAboutData';
|
||||||
|
import { ActiveSearchbox } from '@/components/input/ActiveSearchbox';
|
||||||
|
|
||||||
const CustomMap = dynamic(
|
const CustomMap = dynamic(
|
||||||
() => import('@/components/map/CustomMap'),
|
() => import('@/components/map/CustomMap'),
|
||||||
@@ -34,6 +35,10 @@ function OrderTrackingPage() {
|
|||||||
const [selectedAddress, setSelectedAddress] = useState<NominatimReverseGeocodingResponse | null>(null);
|
const [selectedAddress, setSelectedAddress] = useState<NominatimReverseGeocodingResponse | null>(null);
|
||||||
const [markers, setMarkers] = useState<MarkerData[]>([]);
|
const [markers, setMarkers] = useState<MarkerData[]>([]);
|
||||||
const [initialPositionSet, setInitialPositionSet] = useState(false);
|
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;
|
const restaurant = aboutData?.data;
|
||||||
|
|
||||||
@@ -158,6 +163,72 @@ function OrderTrackingPage() {
|
|||||||
setSelectedAddress(null);
|
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) {
|
if (editMode && isLoadingAddress) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-gray-50 flex items-center justify-center">
|
<div className="fixed inset-0 bg-gray-50 flex items-center justify-center">
|
||||||
@@ -185,20 +256,57 @@ function OrderTrackingPage() {
|
|||||||
onPositionSelect={handlePositionSelect}
|
onPositionSelect={handlePositionSelect}
|
||||||
onZoomChange={handleZoomChange}
|
onZoomChange={handleZoomChange}
|
||||||
onClick={handleMapClick}
|
onClick={handleMapClick}
|
||||||
|
searchEnabled={false}
|
||||||
|
onMapReady={setMapInstance}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute top-4 right-4 z-1000">
|
<div className="absolute top-4 left-4 right-4 z-1001">
|
||||||
<button
|
<div className="flex items-start gap-2" dir="ltr">
|
||||||
onClick={() => router.back()}
|
{!isSearchOpen && (
|
||||||
className="p-2 rounded-full bg-container/90 backdrop-blur-sm shadow-sm"
|
<button
|
||||||
>
|
onClick={() => router.back()}
|
||||||
<ArrowLeft size={24} className="stroke-foreground" />
|
className="p-2 rounded-full bg-container/90 backdrop-blur-sm shadow-sm shrink-0"
|
||||||
</button>
|
>
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
{hasServiceArea && (
|
{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">
|
<div className="bg-blue-50/90 border border-blue-200 rounded-lg p-3 shadow-sm">
|
||||||
<p className="text-xs text-blue-800">
|
<p className="text-xs text-blue-800">
|
||||||
محدوده سرویسدهی روی نقشه با رنگ آبی مشخص شده است
|
محدوده سرویسدهی روی نقشه با رنگ آبی مشخص شده است
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ interface CustomMapProps {
|
|||||||
onClick?: (event: L.LeafletMouseEvent) => void;
|
onClick?: (event: L.LeafletMouseEvent) => void;
|
||||||
searchEnabled?: boolean
|
searchEnabled?: boolean
|
||||||
markerActive?: boolean
|
markerActive?: boolean
|
||||||
|
onMapReady?: (map: L.Map) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component to handle map events and position updates
|
// Component to handle map events and position updates
|
||||||
@@ -114,6 +115,7 @@ const CustomMap: React.FC<CustomMapProps> = ({
|
|||||||
onClick,
|
onClick,
|
||||||
markerActive = true,
|
markerActive = true,
|
||||||
searchEnabled = true,
|
searchEnabled = true,
|
||||||
|
onMapReady,
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null);
|
const [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null);
|
||||||
const [mapInstance, setMapInstance] = useState<L.Map | null>(null);
|
const [mapInstance, setMapInstance] = useState<L.Map | null>(null);
|
||||||
@@ -136,6 +138,13 @@ const CustomMap: React.FC<CustomMapProps> = ({
|
|||||||
checkTiles();
|
checkTiles();
|
||||||
}, [mapInstance]);
|
}, [mapInstance]);
|
||||||
|
|
||||||
|
// Notify parent when map is ready
|
||||||
|
useEffect(() => {
|
||||||
|
if (mapInstance && onMapReady) {
|
||||||
|
onMapReady(mapInstance);
|
||||||
|
}
|
||||||
|
}, [mapInstance, onMapReady]);
|
||||||
|
|
||||||
// Initialize default icon on client side only
|
// Initialize default icon on client side only
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Dynamic import of Leaflet on the client side
|
// Dynamic import of Leaflet on the client side
|
||||||
|
|||||||
Reference in New Issue
Block a user