feat: thêm nút tìm kiếm địa điểm cho phần tạo tour, phần thêm địa điểm
This commit is contained in:
@@ -24,6 +24,8 @@ import {
|
||||
List,
|
||||
Map as MapIconLucide,
|
||||
MapPin,
|
||||
Search,
|
||||
Loader2,
|
||||
Flag,
|
||||
Clock,
|
||||
Check,
|
||||
@@ -198,6 +200,29 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
const [commentLocationName, setCommentLocationName] = useState('');
|
||||
const [joinRequestActionId, setJoinRequestActionId] = useState<string | null>(null);
|
||||
|
||||
// State tìm kiếm cho chế độ Bản đồ trong Tab Lộ trình
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
|
||||
const handleSearchLocation = async (query: string) => {
|
||||
setSearchQuery(query);
|
||||
if (query.trim().length < 2) {
|
||||
setSearchResults([]);
|
||||
return;
|
||||
}
|
||||
setIsSearching(true);
|
||||
try {
|
||||
const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=5&addressdetails=1&namedetails=1&accept-language=vi`);
|
||||
const data = await res.json();
|
||||
setSearchResults(data);
|
||||
} catch (e) {
|
||||
console.error("Lỗi tìm kiếm:", e);
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchTour = useTourStore(state => state.fetchTour);
|
||||
const fetchPublicTourDetails = useTourStore(state => state.fetchPublicTourDetails); // New action
|
||||
|
||||
@@ -803,6 +828,53 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
}} isPublicView={isPublicView} />
|
||||
) : (
|
||||
<div className="h-[60vh] w-full rounded-3xl overflow-hidden shadow-xl border-4 border-white relative">
|
||||
{/* Search Bar Overlay - Thanh tìm kiếm địa điểm trên bản đồ lộ trình */}
|
||||
{!isPublicView && (
|
||||
<div className="absolute top-4 right-4 z-[1001] w-64 md:w-80">
|
||||
<div className="relative group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Tìm địa điểm để ghim..."
|
||||
className="w-full pl-10 pr-10 py-3 bg-white/95 backdrop-blur-md border border-white/20 rounded-2xl shadow-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all text-sm font-bold"
|
||||
value={searchQuery}
|
||||
onChange={e => handleSearchLocation(e.target.value)}
|
||||
/>
|
||||
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-blue-500" />
|
||||
{isSearching ? (
|
||||
<Loader2 className="absolute right-3.5 top-1/2 -translate-y-1/2 w-4 h-4 animate-spin text-blue-500" />
|
||||
) : searchQuery && (
|
||||
<button onClick={() => { setSearchQuery(''); setSearchResults([]); }} className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-red-500 transition-colors">
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Kết quả tìm kiếm */}
|
||||
{searchResults.length > 0 && (
|
||||
<div className="absolute left-0 right-0 mt-2 bg-white/95 backdrop-blur-md border border-gray-100 rounded-2xl shadow-2xl overflow-hidden max-h-48 overflow-y-auto animate-in slide-in-from-top-2 duration-200">
|
||||
{searchResults.map((result, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
setMapCenter([parseFloat(result.lat), parseFloat(result.lon)]);
|
||||
setSearchResults([]);
|
||||
setSearchQuery('');
|
||||
notify({
|
||||
title: 'Tìm thấy địa điểm',
|
||||
message: `Đã di chuyển bản đồ tới: ${result.namedetails?.name || result.display_name.split(',')[0]}`,
|
||||
type: 'success'
|
||||
});
|
||||
}}
|
||||
className="w-full text-left px-4 py-3 hover:bg-blue-50 border-b border-gray-50 last:border-0 transition-colors"
|
||||
>
|
||||
<div className="font-bold text-xs text-gray-900 truncate">{result.namedetails?.name || result.display_name.split(',')[0]}</div>
|
||||
<div className="text-[10px] text-gray-500 truncate leading-tight">{result.display_name}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<MapContainer
|
||||
center={initialViewState?.center || mapCenter}
|
||||
zoom={mapZoom}
|
||||
|
||||
Reference in New Issue
Block a user