🎯 MapView v2.0 - Global Deployment Ready
✨ MAJOR FEATURES: • Auto-zoom intelligence với smart bounds fitting • Enhanced 3D GPS markers với pulsing effects • Professional route display với 6-layer rendering • Status-based parking icons với availability indicators • Production-ready build optimizations 🗺️ AUTO-ZOOM FEATURES: • Smart bounds fitting cho GPS + selected parking • Adaptive padding (50px) cho visual balance • Max zoom control (level 16) để tránh quá gần • Dynamic centering khi không có selection 🎨 ENHANCED VISUALS: • 3D GPS marker với multi-layer pulse effects • Advanced parking icons với status colors • Selection highlighting với animation • Dimming system cho non-selected items 🛣️ ROUTE SYSTEM: • OpenRouteService API integration • Multi-layer route rendering (glow, shadow, main, animated) • Real-time distance & duration calculation • Visual route info trong popup 📱 PRODUCTION READY: • SSR safe với dynamic imports • Build errors resolved • Global deployment via Vercel • Optimized performance 🌍 DEPLOYMENT: • Vercel: https://whatever-ctk2auuxr-phong12hexdockworks-projects.vercel.app • Bundle size: 22.8 kB optimized • Global CDN distribution • HTTPS enabled 💾 VERSION CONTROL: • MapView-v2.0.tsx backup created • MAPVIEW_VERSIONS.md documentation • Full version history tracking
This commit is contained in:
213
frontend/src/app/page-hcmc.tsx
Normal file
213
frontend/src/app/page-hcmc.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Header } from '@/components/Header';
|
||||
import { ParkingList } from '@/components/parking/ParkingList';
|
||||
import { HCMCGPSSimulator } from '@/components/HCMCGPSSimulator';
|
||||
// import { ErrorMessage } from '@/components/ui/ErrorMessage';
|
||||
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
|
||||
import { useParkingSearch } from '@/hooks/useParkingSearch';
|
||||
import { useRouting } from '@/hooks/useRouting';
|
||||
import { ParkingLot, UserLocation, TransportationMode } from '@/types';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
// Dynamic import for map component (client-side only)
|
||||
const MapView = dynamic(
|
||||
() => import('@/components/map/MapView').then((mod) => mod.MapView),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="h-full flex items-center justify-center bg-gray-100 rounded-lg">
|
||||
<LoadingSpinner size="lg" />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
export default function ParkingFinderPage() {
|
||||
// State management
|
||||
const [selectedParkingLot, setSelectedParkingLot] = useState<ParkingLot | null>(null);
|
||||
const [userLocation, setUserLocation] = useState<UserLocation | null>(null);
|
||||
const [searchRadius, setSearchRadius] = useState(4000); // meters - bán kính 4km
|
||||
const [sortType, setSortType] = useState<'availability' | 'price' | 'distance'>('availability');
|
||||
|
||||
// Fixed to car mode only
|
||||
const transportationMode: TransportationMode = 'auto';
|
||||
|
||||
// Custom hooks
|
||||
const {
|
||||
parkingLots,
|
||||
error: parkingError,
|
||||
searchLocation
|
||||
} = useParkingSearch();
|
||||
|
||||
const {
|
||||
route,
|
||||
isLoading: routeLoading,
|
||||
error: routeError,
|
||||
calculateRoute,
|
||||
clearRoute
|
||||
} = useRouting();
|
||||
|
||||
// Handle GPS location change from simulator
|
||||
const handleLocationChange = (location: UserLocation) => {
|
||||
setUserLocation(location);
|
||||
|
||||
// Search for parking near the new location
|
||||
if (location) {
|
||||
searchLocation({ latitude: location.lat, longitude: location.lng });
|
||||
toast.success('Đã cập nhật vị trí GPS và tìm kiếm bãi đỗ xe gần đó');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
if (userLocation) {
|
||||
searchLocation({ latitude: userLocation.lat, longitude: userLocation.lng });
|
||||
toast.success('Đã làm mới danh sách bãi đỗ xe');
|
||||
} else {
|
||||
toast.error('Vui lòng chọn vị trí GPS trước');
|
||||
}
|
||||
};
|
||||
|
||||
const handleParkingLotSelect = async (lot: ParkingLot) => {
|
||||
// If the same parking lot is selected again, deselect it
|
||||
if (selectedParkingLot && selectedParkingLot.id === lot.id) {
|
||||
setSelectedParkingLot(null);
|
||||
clearRoute();
|
||||
toast.success('Đã bỏ chọn bãi đỗ xe');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedParkingLot(lot);
|
||||
|
||||
if (userLocation) {
|
||||
try {
|
||||
await calculateRoute(
|
||||
{ latitude: userLocation.lat, longitude: userLocation.lng },
|
||||
{ latitude: lot.lat, longitude: lot.lng },
|
||||
{ mode: 'driving' }
|
||||
);
|
||||
toast.success(`Đã tính đường đến ${lot.name}`);
|
||||
} catch (error) {
|
||||
toast.error('Không thể tính toán đường đi');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearRoute = () => {
|
||||
clearRoute();
|
||||
setSelectedParkingLot(null);
|
||||
toast.success('Đã xóa tuyến đường');
|
||||
};
|
||||
|
||||
// Show error messages
|
||||
useEffect(() => {
|
||||
if (parkingError) {
|
||||
toast.error(parkingError);
|
||||
}
|
||||
}, [parkingError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (routeError) {
|
||||
toast.error(routeError);
|
||||
}
|
||||
}, [routeError]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header
|
||||
title="Smart Parking Finder - TP.HCM"
|
||||
subtitle="Chỉ hỗ trợ ô tô"
|
||||
onClearRoute={route ? handleClearRoute : undefined}
|
||||
/>
|
||||
|
||||
<main className="container mx-auto px-4 py-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6 h-full">
|
||||
{/* Left Column - Map and Parking List */}
|
||||
<div className="lg:col-span-3 space-y-6">
|
||||
{/* Map Section */}
|
||||
<div className="bg-white rounded-xl shadow-lg overflow-hidden">
|
||||
<div className="h-96">
|
||||
<MapView
|
||||
userLocation={userLocation}
|
||||
parkingLots={parkingLots}
|
||||
selectedParkingLot={selectedParkingLot}
|
||||
route={route}
|
||||
onParkingLotSelect={handleParkingLotSelect}
|
||||
isLoading={routeLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Parking List Section */}
|
||||
<div className="bg-white rounded-xl shadow-lg p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
Bãi đỗ xe trong bán kính 4km
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
<EFBFBD> Chỉ hiển thị bãi xe đang mở cửa và còn chỗ trống
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
Làm mới
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!userLocation ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-600">Vui lòng chọn vị trí GPS để tìm bãi đỗ xe</p>
|
||||
</div>
|
||||
) : parkingLots.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-600">Không tìm thấy bãi đỗ xe nào gần vị trí này</p>
|
||||
</div>
|
||||
) : (
|
||||
<ParkingList
|
||||
parkingLots={parkingLots}
|
||||
onSelect={handleParkingLotSelect}
|
||||
selectedId={selectedParkingLot?.id}
|
||||
userLocation={userLocation}
|
||||
sortType={sortType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column - GPS Simulator */}
|
||||
<div className="lg:col-span-1">
|
||||
<HCMCGPSSimulator
|
||||
onLocationChange={handleLocationChange}
|
||||
currentLocation={userLocation}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Show errors */}
|
||||
{parkingError && (
|
||||
<div className="fixed bottom-4 right-4 max-w-sm">
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{parkingError}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{routeError && (
|
||||
<div className="fixed bottom-4 right-4 max-w-sm">
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
|
||||
{routeError}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user