fix: Ngăn bản đồ ExploreMap tự động nhảy về vị trí người dùng khi thay đổi zoom trên mobile
- Tách userPos (dùng cho marker) và mapCenter (dùng cho center bản đồ) thành 2 state riêng biệt
- MapContainer dùng center={mapCenter} cố định thay vì center={userPos} bị GPS thay đổi liên tục
- Xóa component RecenterMap tự động re-center khi userPos thay đổi
- Thêm nút 'Vị trí của tôi' để người dùng chủ động nhấn mới nhảy bản đồ đến vị trí của mình
- requestGpsPosition cập nhật cả mapCenter lẫn store để map thực sự nhảy khi người dùng chủ động yêu cầu
- Các thao tác khác như click gợi ý tìm kiếm, click ảnh trên bản đồ cũng cập nhật mapCenter để map nhảy đúng vị trí
- Áp dụng cho ExploreMap.tsx - bản đồ chính khi người dùng mở app
This commit is contained in:
@@ -39,15 +39,6 @@ const PHOTO_TAG_LABELS: { [key: string]: string } = {
|
|||||||
'thu-cung': '🐕 Thú cưng'
|
'thu-cung': '🐕 Thú cưng'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Component Helper để cập nhật tâm bản đồ khi vị trí người dùng thay đổi
|
|
||||||
function RecenterMap({ position }: { position: [number, number] }) {
|
|
||||||
const map = useMap();
|
|
||||||
useEffect(() => {
|
|
||||||
map.setView(position, map.getZoom());
|
|
||||||
}, [position, map]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Component Helper để đóng menu khi tương tác với bản đồ
|
// Component Helper để đóng menu khi tương tác với bản đồ
|
||||||
function MapEvents({ onMapAction }: { onMapAction: () => void }) {
|
function MapEvents({ onMapAction }: { onMapAction: () => void }) {
|
||||||
useMapEvents({
|
useMapEvents({
|
||||||
@@ -202,7 +193,11 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [userPos, setUserPos] = useState<[number, number]>(initialViewState?.center || [10.7769, 106.7009]);
|
// Map dùng center cố định để không bị GPS tự nhảy vị trí người dùng
|
||||||
|
const defaultCenter = initialViewState?.center || [10.7769, 106.7009];
|
||||||
|
|
||||||
|
const [userPos, setUserPos] = useState<[number, number]>(defaultCenter);
|
||||||
|
const [mapCenter, setLocalMapCenter] = useState<[number, number]>(initialViewState?.center || defaultCenter);
|
||||||
const [mapZoom] = useState(initialViewState?.zoom || 13);
|
const [mapZoom] = useState(initialViewState?.zoom || 13);
|
||||||
const [publicPhotos, setPublicPhotos] = useState<any[]>([]);
|
const [publicPhotos, setPublicPhotos] = useState<any[]>([]);
|
||||||
const [selectedPhoto, setSelectedPhoto] = useState<any | null>(null);
|
const [selectedPhoto, setSelectedPhoto] = useState<any | null>(null);
|
||||||
@@ -262,7 +257,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
const [blacklist, setBlacklist] = useState<any[]>([]);
|
const [blacklist, setBlacklist] = useState<any[]>([]);
|
||||||
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
|
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
|
||||||
const [isBlacklistOpen, setIsBlacklistOpen] = useState(false);
|
const [isBlacklistOpen, setIsBlacklistOpen] = useState(false);
|
||||||
const mapCenter = useTourStore(state => state.mapCenter);
|
const storeMapCenter = useTourStore(state => state.mapCenter);
|
||||||
|
|
||||||
// Recommendations and GPS States
|
// Recommendations and GPS States
|
||||||
const [recommendedLocations, setRecommendedLocations] = useState<any[]>([]);
|
const [recommendedLocations, setRecommendedLocations] = useState<any[]>([]);
|
||||||
@@ -342,6 +337,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
const posArray: [number, number] = [pos.coords.latitude, pos.coords.longitude];
|
const posArray: [number, number] = [pos.coords.latitude, pos.coords.longitude];
|
||||||
setUserGpsPos(posArray);
|
setUserGpsPos(posArray);
|
||||||
setUserPos(posArray);
|
setUserPos(posArray);
|
||||||
|
setLocalMapCenter(posArray);
|
||||||
setMapCenter(posArray);
|
setMapCenter(posArray);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
@@ -435,7 +431,28 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
fetchTrustedUsers();
|
fetchTrustedUsers();
|
||||||
fetchBlacklist();
|
fetchBlacklist();
|
||||||
fetchRecommendations();
|
fetchRecommendations();
|
||||||
|
// Luôn tải danh sách ảnh công khai để hiển thị trên bản đồ cho tất cả mọi người
|
||||||
|
fetchPublicPhotos();
|
||||||
|
|
||||||
|
// Chỉ tải danh sách tour khi người dùng đã đăng nhập và có token
|
||||||
|
if (user || localStorage.getItem('token')) {
|
||||||
|
fetchPublicTours();
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Chỉ lấy vị trí GPS ban đầu để hiển thị marker, KHÔNG tự động nhảy bản đồ đến vị trí đó
|
||||||
|
// Người dùng phải chủ động nhấn nút định vị mới nhảy bản đồ đến vị trí của mình
|
||||||
|
useEffect(() => {
|
||||||
|
if (!initialViewState && navigator.geolocation) {
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(pos) => {
|
||||||
|
const posArray: [number, number] = [pos.coords.latitude, pos.coords.longitude];
|
||||||
|
setUserPos(posArray);
|
||||||
|
},
|
||||||
|
() => console.log("Không thể lấy vị trí người dùng")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [initialViewState]);
|
||||||
const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); // State để điều khiển hiển thị dropdown lọc
|
const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); // State để điều khiển hiển thị dropdown lọc
|
||||||
const [searchQuery, setSearchQuery] = useState(''); // State cho giá trị tìm kiếm
|
const [searchQuery, setSearchQuery] = useState(''); // State cho giá trị tìm kiếm
|
||||||
const [suggestions, setSuggestions] = useState<{ type: 'tour' | 'location', id: string, name: string, lat?: number, lon?: number }[]>([]);
|
const [suggestions, setSuggestions] = useState<{ type: 'tour' | 'location', id: string, name: string, lat?: number, lon?: number }[]>([]);
|
||||||
@@ -576,6 +593,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
} else if (s.lat && s.lon) {
|
} else if (s.lat && s.lon) {
|
||||||
const pos: [number, number] = [s.lat, s.lon];
|
const pos: [number, number] = [s.lat, s.lon];
|
||||||
setUserPos(pos);
|
setUserPos(pos);
|
||||||
|
setLocalMapCenter(pos);
|
||||||
setMapCenter(pos);
|
setMapCenter(pos);
|
||||||
notify({
|
notify({
|
||||||
title: 'Tìm thấy địa điểm',
|
title: 'Tìm thấy địa điểm',
|
||||||
@@ -783,6 +801,18 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
|
|
||||||
{/* Nhóm bên phải: Các thao tác người dùng */}
|
{/* Nhóm bên phải: Các thao tác người dùng */}
|
||||||
<div className="flex items-center gap-2 pointer-events-auto">
|
<div className="flex items-center gap-2 pointer-events-auto">
|
||||||
|
{/* Nút định vị người dùng */}
|
||||||
|
<button
|
||||||
|
onClick={requestGpsPosition}
|
||||||
|
className="w-11 h-11 bg-[var(--surface)] rounded-full shadow-xl hover:bg-blue-100 text-blue-600 transition-all flex items-center justify-center border border-[var(--border)] shrink-0"
|
||||||
|
title="Vị trí của tôi"
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="3" />
|
||||||
|
<path d="M12 2v4M12 18v4M2 12h4M18 12h4" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Nút Ảnh của tôi */}
|
{/* Nút Ảnh của tôi */}
|
||||||
{isLoggedInOrGuest && (
|
{isLoggedInOrGuest && (
|
||||||
<button
|
<button
|
||||||
@@ -890,13 +920,13 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MapContainer
|
<MapContainer
|
||||||
center={userPos}
|
center={mapCenter}
|
||||||
zoom={mapZoom}
|
zoom={mapZoom}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
preferCanvas={true}
|
preferCanvas={true}
|
||||||
attributionControl={false}
|
attributionControl={false}
|
||||||
>
|
>
|
||||||
<TileLayer
|
<TileLayer
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
/>
|
/>
|
||||||
@@ -906,9 +936,6 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
{/* Đóng menu và dropdown khi tương tác bản đồ */}
|
{/* Đóng menu và dropdown khi tương tác bản đồ */}
|
||||||
<MapEvents onMapAction={() => { setShareMenu(null); setSuggestions([]); setIsFilterDropdownOpen(false); }} />
|
<MapEvents onMapAction={() => { setShareMenu(null); setSuggestions([]); setIsFilterDropdownOpen(false); }} />
|
||||||
|
|
||||||
{/* Tự động di chuyển bản đồ đến vị trí người dùng khi tìm thấy tọa độ */}
|
|
||||||
<RecenterMap position={userPos} />
|
|
||||||
|
|
||||||
<MarkerClusterGroup key={`cluster-${filteredTours.length}`} chunkedLoading>
|
<MarkerClusterGroup key={`cluster-${filteredTours.length}`} chunkedLoading>
|
||||||
{filteredTours.map((tour) => {
|
{filteredTours.map((tour) => {
|
||||||
let startLoc = null;
|
let startLoc = null;
|
||||||
@@ -1403,6 +1430,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserPos([item.latitude, item.longitude]);
|
setUserPos([item.latitude, item.longitude]);
|
||||||
|
setLocalMapCenter([item.latitude, item.longitude]);
|
||||||
setMapCenter([item.latitude, item.longitude]);
|
setMapCenter([item.latitude, item.longitude]);
|
||||||
}}
|
}}
|
||||||
className="text-[9px] font-bold text-blue-600 dark:text-blue-400 text-left hover:underline flex items-center gap-1 mt-0.5 cursor-pointer bg-transparent border-none p-0"
|
className="text-[9px] font-bold text-blue-600 dark:text-blue-400 text-left hover:underline flex items-center gap-1 mt-0.5 cursor-pointer bg-transparent border-none p-0"
|
||||||
@@ -1481,6 +1509,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserPos([item.latitude, item.longitude]);
|
setUserPos([item.latitude, item.longitude]);
|
||||||
|
setLocalMapCenter([item.latitude, item.longitude]);
|
||||||
setMapCenter([item.latitude, item.longitude]);
|
setMapCenter([item.latitude, item.longitude]);
|
||||||
}}
|
}}
|
||||||
className="text-[9px] font-bold text-blue-600 dark:text-blue-400 text-left hover:underline flex items-center gap-1 mt-0.5 cursor-pointer bg-transparent border-none p-0"
|
className="text-[9px] font-bold text-blue-600 dark:text-blue-400 text-left hover:underline flex items-center gap-1 mt-0.5 cursor-pointer bg-transparent border-none p-0"
|
||||||
|
|||||||
Reference in New Issue
Block a user