diff --git a/package-lock.json b/package-lock.json index c87708f..d3039d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,17 @@ "name": "antigravity_dev", "version": "0.1.0", "dependencies": { + "@types/leaflet": "^1.9.21", "@types/react-google-recaptcha": "^2.1.9", "axios": "^1.13.2", + "leaflet": "^1.9.4", "lucide-react": "^0.554.0", "next": "16.0.3", "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", "react-google-recaptcha": "^3.1.0", + "react-leaflet": "^5.0.0", "selenium-webdriver": "^4.38.0", "sitemap": "^9.0.0", "tailwindcss-animate": "^1.0.7", @@ -1241,6 +1244,17 @@ "node": ">=12.4.0" } }, + "node_modules/@react-leaflet/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", + "integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1546,6 +1560,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1560,6 +1580,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "20.19.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", @@ -4687,6 +4716,12 @@ "node": ">=0.10" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5668,6 +5703,20 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-leaflet": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz", + "integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^3.0.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", diff --git a/package.json b/package.json index d06df5c..f326764 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,17 @@ "lint": "eslint" }, "dependencies": { + "@types/leaflet": "^1.9.21", "@types/react-google-recaptcha": "^2.1.9", "axios": "^1.13.2", + "leaflet": "^1.9.4", "lucide-react": "^0.554.0", "next": "16.0.3", "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", "react-google-recaptcha": "^3.1.0", + "react-leaflet": "^5.0.0", "selenium-webdriver": "^4.38.0", "sitemap": "^9.0.0", "tailwindcss-animate": "^1.0.7", diff --git a/src/components/ConnectivityMap.tsx b/src/components/ConnectivityMap.tsx new file mode 100644 index 0000000..bb51cfa --- /dev/null +++ b/src/components/ConnectivityMap.tsx @@ -0,0 +1,268 @@ +"use client"; + +import { useState, useEffect, useMemo } from 'react'; +import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet'; +import L from 'leaflet'; +import 'leaflet/dist/leaflet.css'; + +// Fix for default markers in Next.js +const DefaultIcon = L.icon({ + iconUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png', + shadowUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png', + iconSize: [25, 41], + iconAnchor: [12, 41] +}); +L.Marker.prototype.options.icon = DefaultIcon; + +// Custom Icons +const createCustomIcon = (color: string, number?: string) => { + return L.divIcon({ + className: 'custom-icon', + html: `
${number || ''}
`, + iconSize: [36, 36], + iconAnchor: [18, 18], + }); +}; + +const homeIcon = L.divIcon({ + className: 'home-icon', + html: `
+ + + + +
`, + iconSize: [48, 48], + iconAnchor: [24, 24], +}); + + +// Mock Data +type LocationData = { + category: string; + items: { + id: number; + name: string; + dist: string; + time: string; + lat: number; + lng: number; + count: number; + }[]; +}; + +const PROPERTY_LOCATION: [number, number] = [12.9385, 77.7297]; // Approximate Varthur + +const MOCK_DATA: Record = { + 'Commute': [ + { id: 1, name: "Dommasandra Circle Metro Station", dist: "3.62 Km", time: "7 mins", lat: 12.9250, lng: 77.7450, count: 6 }, + { id: 2, name: "Sompura Metro Station", dist: "7.05 Km", time: "15 mins", lat: 12.9100, lng: 77.7600, count: 4 }, + { id: 3, name: "Sarjapur Metro Station", dist: "8.28 Km", time: "18 mins", lat: 12.8900, lng: 77.7800, count: 3 }, + { id: 4, name: "Ambedkar nagar Metro Station", dist: "8.89 Km", time: "18 mins", lat: 12.9550, lng: 77.7100, count: 5 }, + ], + 'Education': [ + { id: 5, name: "Whitefield Global School", dist: "2.5 Km", time: "6 mins", lat: 12.9550, lng: 77.7350, count: 8 }, + { id: 6, name: "Greenwood High", dist: "4.1 Km", time: "10 mins", lat: 12.9150, lng: 77.7550, count: 5 }, + ], + 'Hospitals': [ + { id: 7, name: "Manipal Hospital Varthur", dist: "1.2 Km", time: "4 mins", lat: 12.9420, lng: 77.7320, count: 2 }, + ], + 'Work': [ + { id: 8, name: "RGA Tech Park", dist: "5.5 Km", time: "12 mins", lat: 12.9050, lng: 77.7150, count: 12 }, + ], + 'Entertainment': [ + { id: 9, name: "Nexus Whitefield", dist: "3.2 Km", time: "9 mins", lat: 12.9600, lng: 77.7400, count: 7 }, + ], +}; + +function MapController({ center }: { center: [number, number] }) { + const map = useMap(); + useEffect(() => { + map.setView(center, 13); + }, [center, map]); + return null; +} + +function ZoomHandler({ zoomIn, zoomOut }: { zoomIn: () => void, zoomOut: () => void }) { + return ( +
+ + +
+ ); +} + + +export default function ConnectivityMap() { + const [activeTab, setActiveTab] = useState("Commute"); + const [searchQuery, setSearchQuery] = useState(""); + const [mapZoom, setMapZoom] = useState(13); + const [mapRef, setMapRef] = useState(null); + + const activeData = useMemo(() => { + let data = activeTab === "Search" + ? Object.values(MOCK_DATA).flat() + : MOCK_DATA[activeTab] || []; + + if (searchQuery) { + data = data.filter(item => + item.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + } + return data; + }, [activeTab, searchQuery]); + + const handleZoomIn = () => { + if (mapRef) mapRef.zoomIn(); + }; + + const handleZoomOut = () => { + if (mapRef) mapRef.zoomOut(); + }; + + + return ( +
+ {/* Tabs */} +
+ {["Commute", "Education", "Hospitals", "Work", "Entertainment", "Search"].map((tab) => ( + + ))} +
+ +
+ {/* Map Area */} +
+ + + + + + {/* Property Marker */} + + + {/* POI Markers */} + {activeData.map((item) => ( + + {item.name} + + ))} + + + {/* Layer Toggle Button (Bottom Left) */} +
+ +
+
+ + {/* Floating List Card (Right Side) */} +
+
+ {/* Search Input for Search Tab */} + {activeTab === "Search" && ( +
+ setSearchQuery(e.target.value)} + className="w-full px-4 py-2 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-orange-500" + /> +
+ )} + +
+ {activeData.length > 0 ? ( + activeData.map((item) => ( +
+

{item.name}

+
+ {item.dist} + | + {item.time} +
+
+ )) + ) : ( +
+ No results found +
+ )} +
+
+
+
+
+ ); +} diff --git a/src/components/PropertyDetailClient.tsx b/src/components/PropertyDetailClient.tsx index 6bf61fd..c8fd3e6 100644 --- a/src/components/PropertyDetailClient.tsx +++ b/src/components/PropertyDetailClient.tsx @@ -9,6 +9,12 @@ import InnerBanner from "@/components/InnerBanner"; import { Property } from "@/data/properties"; import axios from "axios"; import { useCompare } from "@/context/CompareContext"; +import dynamic from 'next/dynamic'; + +const ConnectivityMap = dynamic(() => import('./ConnectivityMap'), { + ssr: false, + loading: () =>
+}); interface FormData { name: string; @@ -595,128 +601,7 @@ export default function PropertyDetailClient({ property }: { property: Property
{/* Map Interface */} -
- {/* Tabs */} -
- {["Commute", "Education", "Hospitals", "Work", "Entertainment ", "Search"].map((tab) => ( - - ))} -
- -
- {/* Map Area */} -
- {/* Map Image Placeholder */} -
- Map View -
- - {/* Map Controls */} -
- - -
- - {/* Layer Toggle */} -
- -
- - {/* Pins (Static for demo) */} -
-
6
-
-
-
6
-
-
-
7
-
-
-
- - - - -
-
-
- - {/* List Area */} -
-
- {[ - { name: "Dommasandra Circle Metro Station", dist: "3.62 Km", time: "7 mins" }, - { name: "Sompura Metro Station", dist: "7.05 Km", time: "15 mins" }, - { name: "Sarjapur Metro Station", dist: "8.28 Km", time: "18 mins" }, - { name: "Ambedkar nagar Metro Station", dist: "8.89 Km", time: "18 mins" }, - ].map((item, idx) => ( -
-

{item.name}

-
- {item.dist} - | - {item.time} -
-
- ))} -
-
-
-
+ {/* Master Plan Section */} diff --git a/src/components/PropertyNav.tsx b/src/components/PropertyNav.tsx index 40e81aa..4beae26 100644 --- a/src/components/PropertyNav.tsx +++ b/src/components/PropertyNav.tsx @@ -44,7 +44,7 @@ export default function PropertyNav({ sections }: PropertyNavProps) { }; return ( -
+